diff --git a/.eslintignore b/.eslintignore index 92a9a83e..2ef2464d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,3 +11,4 @@ releases .tmp .env scripts +vitest.config.ts diff --git a/.gitignore b/.gitignore index 3d725187..18964234 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ node_modules .idea typedocs dist -dist-cfg -dist-vitest +dist-* examples/dist-tsc visual-regression/failed-results visual-regression/certified-snapshots/*-local diff --git a/Dockerfile b/Dockerfile index 253dae7b..e0937ed3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,13 +4,13 @@ FROM mcr.microsoft.com/playwright:v1.39.0-jammy # Set the working directory WORKDIR /work -# Install PNPM -RUN npm install -g pnpm - # Copy the necessary files to the container COPY .npmrc .npmrc COPY package.json package.json +# Install PNPM +RUN corepack prepare && corepack enable + # Get pnpm to install the version of Node declared in .npmrc RUN pnpm exec ls diff --git a/README.md b/README.md index ced14933..141c7300 100644 --- a/README.md +++ b/README.md @@ -89,145 +89,42 @@ See [docs/ManualRegressionTests.md]. See [RELEASE.md](./RELEASE.md) -## Main Space vs Core Space +## Installing Fonts -The Lightning 3 Renderer runs code in two logically seperate environments: -the **Main Space** and the **Core Space**. - -Users of the Renderer will write most of their code for the Main Space using -the **Main API**. This is code that will always run on the browser's main thread -and includes initializing the Renderer, creating/modifying/destroying nodes, -controlling animations, etc. - -The Core Space is where the actual rendering of each UI frame happens and is -mostly meant to be transparent to users of the Renderer. However, the Core Space -is where all of the code that must be tightly coupled to the rendering process -must be loaded and run. The Core Space is extendible by users by writing -**Core Extensions** via the **Core API**. This allows for users to develop their -own shaders, textures, text renderers, dynamic shader effects, and more. Fonts -used in an application must be loaded in this way too. The Core Space exists -seperately from the Main Space because it is allowed to execute on the page's -main thread OR a Web Worker thread. A **Core Driver** (see below) is used to -bridge the Main Space with the Core Space. - -## Core Drivers - -The Lightning 3 Renderer is designed to be able to use a single thread or -multiple web worker threads based on the configuration of a **Core Driver**. - -A Core Driver essentially acts as a bridge between the Main and Core spaces -defined above. - -The Renderer comes with two Core Drivers: the Main Core Driver for single -threaded rendering and the ThreadX Core Driver for multi-threaded rendering. - -NOTE: The ThreadX Core Driver is experimental and even when the Renderer -graduates from beta may still not be ready for production use. - -### Main Core Driver - -The Main Core Driver renders your application on the web page's main thread. - -It can be configured into the Renderer like so: - -```ts -import { MainCoreDriver, RendererMain } from '@lightningjs/renderer'; - -const renderer = new RendererMain( - { - // App Config - }, - 'app', // App div ID - new MainCoreDriver(), // Main Render driver -); - -// ... -``` - -### ThreadX Core Driver - -The ThreadX Core Driver renders your application on a seperately spawned -Web Worker thread. - -It can be configured into the Renderer like so: +Fonts can be installed into the Font Manager exposed by the Renderer's Stage. +There are two types of fonts that you can install, Web/Canvas2D fonts (WebTrFontFace) +and SDF fonts (SdfTrFontFace). Install that fonts that your applications needs +at start up so they are ready when your application is rendered. ```ts import { - ThreadXCoreDriver, RendererMain, + WebTrFontFace, + SdfTrFontFace, } from '@lightningjs/renderer'; -// The `@lightningjs/vite-plugin-import-chunk-url` Vite plugin is required for this: -import coreWorkerUrl from './common/CoreWorker.js?importChunkUrl'; - const renderer = new RendererMain( { - // App Config + appWidth: 1920, + appHeight: 1080, + // ...Other Renderer Config }, - 'app', // App div ID - new ThreadXCoreDriver({ - coreWorkerUrl, - }); + 'app', // id of div to insert Canvas. ); -``` - -## Core Extensions - -To load fonts, and/or other custom code into the Core Space, you must write a -Core Extension and pass it via dynamically importable URL to the initialization -of the Renderer. -Just like with loading the ThreadX Core Web Worker for the ThreadX, you import -your core extension using the `@lightningjs/vite-plugin-import-chunk-url` plugin so that -it's code is bundled and loaded seperately from your main app's bundle. - -You can write a Core Extension by extending the CoreExtension class from the -Core API like so: - -```ts -import { - CoreExtension, - WebTrFontFace, - SdfTrFontFace, - type Stage, -} from '@lightning/renderer/core'; - -export default class MyCoreExtension extends CoreExtension { - async run(stage: Stage) { - // Load fonts into core - stage.fontManager.addFontFace( - new WebTrFontFace('Ubuntu', {}, '/fonts/Ubuntu-Regular.ttf'), - ); - - stage.fontManager.addFontFace( - new SdfTrFontFace( - 'Ubuntu', - {}, - 'msdf', - stage, - '/fonts/Ubuntu-Regular.msdf.png', - '/fonts/Ubuntu-Regular.msdf.json', - ), - ); - } -} -``` - -And then in your application's main entry point you can import it using -`@lightningjs/vite-plugin-import-chunk-url`: - -```ts -import coreExtensionModuleUrl from './MyCoreExtension.js?importChunkUrl'; - -// Set up driver, etc. +// Load fonts into renderer +renderer.stage.fontManager.addFontFace( + new WebTrFontFace('Ubuntu', {}, '/fonts/Ubuntu-Regular.ttf'), +); -// Initialize the Renderer -const renderer = new RendererMain( - { - // Other Renderer Config... - coreExtensionModule: coreExtensionModuleUrl, - }, - 'app', - driver, +renderer.stage.fontManager.addFontFace( + new SdfTrFontFace( + 'Ubuntu', + {}, + 'msdf', + stage, + '/fonts/Ubuntu-Regular.msdf.png', + '/fonts/Ubuntu-Regular.msdf.json', + ), ); ``` diff --git a/docs/CustomShaderEffectTexture.md b/docs/CustomShaderEffectTexture.md new file mode 100644 index 00000000..ce295db2 --- /dev/null +++ b/docs/CustomShaderEffectTexture.md @@ -0,0 +1,5 @@ +# Custom Shaders / Effects / Textures + +For some examples on how to create custom shaders, effects and textures, see +the (custom-shader-effect-texture)[./example-projects/custom-shader-effect-texture] +example project. diff --git a/docs/ManualRegressionTests.md b/docs/ManualRegressionTests.md index efc58172..e688a5aa 100644 --- a/docs/ManualRegressionTests.md +++ b/docs/ManualRegressionTests.md @@ -4,49 +4,49 @@ In addition to the automated [Visual Regression Tests](../visual-regression/READ there are some tests that should be run manually on embedded devices to ensure the Renderer is working properly. -## Reference-Based Texture Memory Management Test +## Critical Texture Memory Cleanup Test -`?test=reference-texture-memory` +`?test=texture-cleanup-critical&monitor=true` -This test confirms the Renderer's ability to proactively garbage collect -textures that are, at the very least, likely to not be referenced anymore in -memory. +This test confirms the Renderer's ability to clean up textures when it never +has a chance to perform Idle Texture Cleanups. Within an infinite loop, the test updates the texture of a single full screen background node to a new random NoiseTexture in rapid succession. -**Expected Results**: The test runs for at least 1 hour without crashing. +**Expected Results**: The tests runs for at least 30 mins minutes without +crashing or the visible Nodes becoming black. The Memory Monitor shows loaded +textures reaching the Critical Threshold and then falling back to the target. -To confirm that the textures are being properly disposed of, you can use the Chrome Task Manager to monitor the GPU's memory usage: +To further confirm that the textures are being properly disposed of, you can use +the Chrome Task Manager to monitor the GPU's memory usage: 1. Click Window > Task Manager 2. Locate the "GPU Process" 3. Observe the "Memory Footprint" column -4. The value should eventually drop significantly toward a minimum and/or reach a - threadhold. +4. Like the Memory Monitor, the value should increase, and fall significantly + repeatedly. -By default, the ManualCountTextureUsageTracker is used to track texture usage. Also test the experimental FinalizationRegistryTextureUsageTracker instead, by setting the URL param "finalizationRegistry=true". +## Idle Texture Memory Cleanup Test -## Threshold-Based Texture Memory Management Test +`?test=texture-cleanup-idle&monitor=true` -`?test=threshold-texture-memory` - -This test confirms the Renderer's ability to garbage collect textures from GPU VRAM -that are still referenced and assigned to Nodes but no longer visible within -the configured `boundsMargin` when the configured `txMemByteThreshold` is -exceeded. +This test confirms the Renderer's ability to clean up textures that are no longer +renderable (not in the configured `boundsMargin`) from GPU VRAM when the Renderer +becomes idle. Within an infinite loop, this test generates a grid of Nodes with random NoiseTextures assigned first completely visible on screen (for at least a frame) and then moves them outside of the configured `boundsMargin` before repeating the loop. -**Expected Results**: The tests runs for at least XXXX minutes on an XXXX running WPE -without crashing or the visible Nodes becoming blank. +**Expected Results**: The tests runs for at least 30 mins minutes without +crashing or the visible Nodes becoming black. The Memory Monitor shows loaded +textures falling to the Target Threshold roughly every 5 seconds. -To test that the textures are being properly disposed of, you can use the Chrome Task Manager to monitor the GPU's memory usage: +To further test that the textures are being properly disposed of, you can use the Chrome Task Manager to monitor the GPU's memory usage: 1. Click Window > Task Manager 2. Locate the "GPU Process" 3. Observe the "Memory Footprint" column -4. The value should eventually drop significantly toward a minimum and/or reach a - threshold. +4. Like the Memory Monitor, the value should increase, and fall significantly + repeatedly. diff --git a/docs/example-projects/custom-shader-effect-texture/index.html b/docs/example-projects/custom-shader-effect-texture/index.html new file mode 100644 index 00000000..9678f140 --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/index.html @@ -0,0 +1,21 @@ + + + Renderer Browser Test + + + +
+ + + diff --git a/docs/example-projects/custom-shader-effect-texture/package.json b/docs/example-projects/custom-shader-effect-texture/package.json new file mode 100644 index 00000000..0e8dc56a --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/package.json @@ -0,0 +1,23 @@ +{ + "name": "renderer-custom-shader", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "start": "vite --open --host", + "build": "tsc && vite build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@lightningjs/renderer": "link:../../.." + }, + "devDependencies": { + "@types/node": "^20.12.12", + "typescript": "^5.4.5", + "vite": "^5.2.11" + } +} diff --git a/docs/example-projects/custom-shader-effect-texture/pnpm-lock.yaml b/docs/example-projects/custom-shader-effect-texture/pnpm-lock.yaml new file mode 100644 index 00000000..012878dc --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/pnpm-lock.yaml @@ -0,0 +1,505 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@lightningjs/renderer': + specifier: link:../../.. + version: link:../../.. + devDependencies: + '@types/node': + specifier: ^20.12.12 + version: 20.14.10 + typescript: + specifier: ^5.4.5 + version: 5.5.3 + vite: + specifier: ^5.2.11 + version: 5.3.3(@types/node@20.14.10) + +packages: + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm-eabi@4.18.1: + resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.18.1: + resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.18.1: + resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.18.1: + resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.18.1: + resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.18.1: + resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.18.1: + resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.18.1: + resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.18.1: + resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.18.1: + resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.18.1: + resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.18.1: + resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.18.1: + resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.18.1: + resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.18.1: + resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.18.1: + resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/node@20.14.10: + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + dependencies: + undici-types: 5.26.5 + dev: true + + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + dev: true + + /postcss@8.4.39: + resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + dev: true + + /rollup@4.18.1: + resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.1 + '@rollup/rollup-android-arm64': 4.18.1 + '@rollup/rollup-darwin-arm64': 4.18.1 + '@rollup/rollup-darwin-x64': 4.18.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.1 + '@rollup/rollup-linux-arm-musleabihf': 4.18.1 + '@rollup/rollup-linux-arm64-gnu': 4.18.1 + '@rollup/rollup-linux-arm64-musl': 4.18.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 + '@rollup/rollup-linux-riscv64-gnu': 4.18.1 + '@rollup/rollup-linux-s390x-gnu': 4.18.1 + '@rollup/rollup-linux-x64-gnu': 4.18.1 + '@rollup/rollup-linux-x64-musl': 4.18.1 + '@rollup/rollup-win32-arm64-msvc': 4.18.1 + '@rollup/rollup-win32-ia32-msvc': 4.18.1 + '@rollup/rollup-win32-x64-msvc': 4.18.1 + fsevents: 2.3.3 + dev: true + + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + + /typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /vite@5.3.3(@types/node@20.14.10): + resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.14.10 + esbuild: 0.21.5 + postcss: 8.4.39 + rollup: 4.18.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true diff --git a/docs/example-projects/custom-shader-effect-texture/pnpm-workspace.yaml b/docs/example-projects/custom-shader-effect-texture/pnpm-workspace.yaml new file mode 100644 index 00000000..e69de29b diff --git a/docs/example-projects/custom-shader-effect-texture/src/MyCustomEffect.ts b/docs/example-projects/custom-shader-effect-texture/src/MyCustomEffect.ts new file mode 100644 index 00000000..5e2099f3 --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/src/MyCustomEffect.ts @@ -0,0 +1,77 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + type DefaultEffectProps, + ShaderEffect, + type ShaderEffectUniforms, +} from '@lightningjs/renderer'; + +/** + * Augment the EffectMap interface to include the CustomEffect + */ +declare module '@lightningjs/renderer' { + interface EffectMap { + MyCustomEffect: typeof MyCustomEffect; + } +} + +/** + * Properties of the {@link MyCustomEffect} effect + */ +export interface MyCustomEffectProps extends DefaultEffectProps { + /** + * Grey scale amount between 0 - 1. + * + * @default 1 + */ + amount?: number; +} + +/** + * Grayscale effect grayscales the color values of the current mask color + */ +export class MyCustomEffect extends ShaderEffect { + static z$__type__Props: MyCustomEffectProps; + override readonly name = 'MyCustomEffect'; + + static override getEffectKey(): string { + return `MyCustomEffect`; + } + + static override resolveDefaults( + props: MyCustomEffectProps, + ): Required { + return { + amount: props.amount ?? 1, + }; + } + + static override uniforms: ShaderEffectUniforms = { + amount: { + value: 1, + method: 'uniform1f', + type: 'float', + }, + }; + + static override onColorize = ` + float grayness = 0.2 * maskColor.r + 0.6 * maskColor.g + 0.2 * maskColor.b; + return vec4(amount * vec3(grayness) + (1.0 - amount) * maskColor.rgb, maskColor.a); + `; +} diff --git a/docs/example-projects/custom-shader-effect-texture/src/MyCustomShader.ts b/docs/example-projects/custom-shader-effect-texture/src/MyCustomShader.ts new file mode 100644 index 00000000..eea844bd --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/src/MyCustomShader.ts @@ -0,0 +1,245 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Based on https://www.shadertoy.com/view/lsVSWW + * + * @module + */ +import { + WebGlCoreShader, + type DimensionsShaderProp, + type WebGlCoreRenderer, + type WebGlCoreCtxTexture, + type ShaderProgramSources, +} from '@lightningjs/renderer'; + +export interface Point { + x: number; + y: number; +} + +declare module '@lightningjs/renderer' { + interface ShaderMap { + MyCustomShader: typeof MyCustomShader; + } +} + +/** + * Properties of the {@link CustomShaderProps} shader + */ +export interface CustomShaderProps extends DimensionsShaderProp { + /** + * Use normalized values rather than pixle values + * @default false + */ + normalized?: boolean; + + /** + * x & y coordinates of the top left point + * @default null + */ + topLeft?: Point | null; + + /** + * x & y coordinates of the top right point + * @default null + */ + topRight?: Point | null; + + /** + * x & y coordinates of the bottom right point + * @default null + */ + bottomRight?: Point | null; + + /** + * x & y coordinates of the bottom left point + * @default null + */ + bottomLeft?: Point | null; +} + +export class MyCustomShader extends WebGlCoreShader { + constructor(renderer: WebGlCoreRenderer) { + super({ + renderer, + attributes: ['a_position', 'a_textureCoordinate', 'a_color'], + uniforms: [ + { name: 'u_resolution', uniform: 'uniform2fv' }, + { name: 'u_pixelRatio', uniform: 'uniform1f' }, + { name: 'u_texture', uniform: 'uniform2f' }, + { name: 'u_dimensions', uniform: 'uniform2fv' }, + { name: 'u_topLeft', uniform: 'uniform2fv' }, + { name: 'u_topRight', uniform: 'uniform2fv' }, + { name: 'u_bottomRight', uniform: 'uniform2fv' }, + { name: 'u_bottomLeft', uniform: 'uniform2fv' }, + ], + }); + } + + static z$__type__Props: CustomShaderProps; + + static override resolveDefaults( + props: CustomShaderProps, + ): Required { + return { + normalized: props.normalized || false, + topLeft: props.topLeft || null, + topRight: props.topRight || null, + bottomRight: props.bottomRight || null, + bottomLeft: props.bottomLeft || null, + $dimensions: { + width: 0, + height: 0, + }, + }; + } + + override bindTextures(textures: WebGlCoreCtxTexture[]) { + const { glw } = this; + glw.activeTexture(0); + glw.bindTexture(textures[0]!.ctxTexture); + } + + protected override bindProps(props: Required): void { + const width = props.normalized ? 1 : props.$dimensions.width; + const height = props.normalized ? 1 : props.$dimensions.height; + + const topLeft = [ + (props.topLeft?.x || 0) / width, + (props.topLeft?.y || 0) / height, + ]; + + const topRight = [ + (props.topRight?.x || width) / width, + (props.topRight?.y || 0) / height, + ]; + + const bottomRight = [ + (props.bottomRight?.x || width) / width, + (props.bottomRight?.y || height) / height, + ]; + + const bottomLeft = [ + (props.bottomLeft?.x || 0) / width, + (props.bottomLeft?.y || height) / height, + ]; + + this.setUniform('u_topLeft', new Float32Array(topLeft)); + this.setUniform('u_topRight', new Float32Array(topRight)); + this.setUniform('u_bottomRight', new Float32Array(bottomRight)); + this.setUniform('u_bottomLeft', new Float32Array(bottomLeft)); + } + + override canBatchShaderProps( + propsA: Required, + propsB: Required, + ): boolean { + return JSON.stringify(propsA) === JSON.stringify(propsB); + } + + static override shaderSources: ShaderProgramSources = { + vertex: ` + # ifdef GL_FRAGMENT_PRESICISON_HIGH + precision highp float; + # else + precision mediump float; + # endif + + attribute vec2 a_position; + attribute vec2 a_textureCoordinate; + attribute vec4 a_color; + + uniform vec2 u_resolution; + uniform float u_pixelRatio; + + varying vec4 v_color; + varying vec2 v_textureCoordinate; + + void main() { + vec2 normalized = a_position * u_pixelRatio / u_resolution; + vec2 zero_two = normalized * 2.0; + vec2 clip_space = zero_two - 1.0; + + // pass to fragment + v_color = a_color; + v_textureCoordinate = a_textureCoordinate; + + // flip y + gl_Position = vec4(clip_space * vec2(1.0, -1.0), 0, 1); + } + `, + fragment: ` + # ifdef GL_FRAGMENT_PRESICISON_HIGH + precision highp float; + # else + precision mediump float; + # endif + + uniform sampler2D u_texture; + uniform vec2 u_topLeft; + uniform vec2 u_topRight; + uniform vec2 u_bottomLeft; + uniform vec2 u_bottomRight; + + varying vec2 v_textureCoordinate; + varying vec4 v_color; + + float xross(in vec2 a, in vec2 b) { + return a.x * b.y - a.y * b.x; + } + + vec2 invBilinear(in vec2 p, in vec2 a, in vec2 b, in vec2 c, in vec2 d ){ + vec2 e = b-a; + vec2 f = d-a; + vec2 g = a-b+c-d; + vec2 h = p-a; + + float k2 = xross(g, f); + float k1 = xross(e, f) + xross(h, g); + float k0 = xross(h, e); + + float w = k1*k1 - 4.0*k0*k2; + + if( w<0.0 ) return vec2(-1.0); + + w = sqrt(w); + + // will fail for k0=0, which is only on the ba edge + float v = 2.0*k0/(-k1 - w); + if( v<0.0 || v>1.0 ) v = 2.0*k0/(-k1 + w); + + float u = (h.x - f.x*v)/(e.x + g.x*v); + if( u<0.0 || u>1.0 || v<0.0 || v>1.0 ) return vec2(-1.0); + return vec2( u, v ); + } + + void main(void){ + vec4 color = vec4(0.0); + vec2 texUv = invBilinear(v_textureCoordinate, u_topLeft, u_topRight, u_bottomRight, u_bottomLeft); + + if (texUv.x > -0.5) { + color = texture2D(u_texture, texUv) * v_color; + } + + gl_FragColor = color; + } + `, + }; +} diff --git a/docs/example-projects/custom-shader-effect-texture/src/MyCustomTexture.ts b/docs/example-projects/custom-shader-effect-texture/src/MyCustomTexture.ts new file mode 100644 index 00000000..f4044c4c --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/src/MyCustomTexture.ts @@ -0,0 +1,72 @@ +import { + Texture, + type TextureData, + CoreTextureManager, +} from '@lightningjs/renderer'; +import { assertTruthy } from '@lightningjs/renderer/utils'; +/** + * Augment the EffectMap interface to include the CustomEffect + */ +declare module '@lightningjs/renderer' { + interface TextureMap { + MyCustomTexture: typeof MyCustomTexture; + } +} + +export interface MyCustomTextureProps { + percent?: number; + width: number; + height: number; +} + +export class MyCustomTexture extends Texture { + static z$__type__Props: MyCustomTextureProps; + + private props: Required; + + constructor(txManager: CoreTextureManager, props: MyCustomTextureProps) { + super(txManager); + this.props = MyCustomTexture.resolveDefaults(props); + } + + override async getTextureData(): Promise { + const { percent, width, height } = this.props; + const radius = Math.min(width, height) / 2; + const angle = 2 * Math.PI * (percent / 100); + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + assertTruthy(ctx); + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, 2 * Math.PI); + ctx.fillStyle = 'orange'; + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(radius, radius); + ctx.arc(radius, radius, radius, -angle / 2, angle / 2); + ctx.closePath(); + ctx.fillStyle = 'blue'; + ctx.fill(); + return { + data: ctx.getImageData(0, 0, canvas.width, canvas.height), + }; + } + + static override makeCacheKey(props: MyCustomTextureProps): string | false { + // // Cache by props (only do this if could be helpful, otherwise leave it uncached) + // const rprops = MyCustomTexture.resolveDefaults(props) + // return `MyCustomTexture,${rprops.percent},${rprops.width},${rprops.height},`; + return false; // <-- Don't cache at all + } + + static override resolveDefaults( + props: MyCustomTextureProps, + ): Required { + return { + percent: props.percent ?? 20, + width: props.width, + height: props.height, + }; + } +} diff --git a/docs/example-projects/custom-shader-effect-texture/src/assets/robot.png b/docs/example-projects/custom-shader-effect-texture/src/assets/robot.png new file mode 100644 index 00000000..e8befeb5 Binary files /dev/null and b/docs/example-projects/custom-shader-effect-texture/src/assets/robot.png differ diff --git a/docs/example-projects/custom-shader-effect-texture/src/index.ts b/docs/example-projects/custom-shader-effect-texture/src/index.ts new file mode 100644 index 00000000..46fbd3cd --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/src/index.ts @@ -0,0 +1,71 @@ +import { RendererMain } from '@lightningjs/renderer'; +import { MyCustomEffect } from './MyCustomEffect.js'; +import { MyCustomShader } from './MyCustomShader.js'; +import { MyCustomTexture } from './MyCustomTexture.js'; +import robotImg from './assets/robot.png'; + +(async () => { + const renderer = new RendererMain( + { + appWidth: 1920, + appHeight: 1080, + boundsMargin: [100, 100, 100, 100], + clearColor: 0x000000ff, + fpsUpdateInterval: 1000, + enableContextSpy: false, + enableInspector: false, + renderMode: 'webgl', + }, + 'app', + ); + + renderer.stage.shManager.registerShaderType('MyCustomShader', MyCustomShader); + renderer.stage.shManager.registerEffectType('MyCustomEffect', MyCustomEffect); + renderer.stage.txManager.registerTextureType( + 'MyCustomTexture', + MyCustomTexture, + ); + + const distortedBot = renderer.createNode({ + width: 300, + height: 300, + src: robotImg, + parent: renderer.root, + shader: renderer.createShader('MyCustomShader', { + normalized: true, + topLeft: { x: 0.5, y: 0 }, + topRight: { x: 0.500001, y: 0 }, + bottomRight: { x: 1, y: 1 }, + bottomLeft: { x: 0, y: 1 }, + }), + }); + + const greyRobot = renderer.createNode({ + x: 300, + width: 300, + height: 300, + parent: renderer.root, + shader: renderer.createDynamicShader([ + renderer.createEffect( + 'MyCustomEffect', + { + amount: 1.0, + }, + 'custom', + ), + ]), + src: robotImg, + }); + + const pacman = renderer.createNode({ + x: 600, + width: 300, + height: 300, + texture: renderer.createTexture('MyCustomTexture', { + percent: 5, + width: 300, + height: 300, + }), + parent: renderer.root, + }); +})().catch(console.error); diff --git a/examples/common/CoreWorker.ts b/docs/example-projects/custom-shader-effect-texture/src/vite-env.d.ts similarity index 86% rename from examples/common/CoreWorker.ts rename to docs/example-projects/custom-shader-effect-texture/src/vite-env.d.ts index b0200772..77dd71a8 100644 --- a/examples/common/CoreWorker.ts +++ b/docs/example-projects/custom-shader-effect-texture/src/vite-env.d.ts @@ -17,4 +17,5 @@ * limitations under the License. */ -import '@lightningjs/renderer/workers/renderer'; +/// +// This enables Vite's import.meta augmentations and possibly other things? diff --git a/docs/example-projects/custom-shader-effect-texture/tsconfig.cfg.json b/docs/example-projects/custom-shader-effect-texture/tsconfig.cfg.json new file mode 100644 index 00000000..e8c9185d --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/tsconfig.cfg.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist-cfg", + "types": ["@types/node"], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "composite": true, + "resolveJsonModule": true, + "allowJs": true, + "esModuleInterop": true, + }, + "files": [ + "./vite.config.ts", + ] +} diff --git a/docs/example-projects/custom-shader-effect-texture/tsconfig.json b/docs/example-projects/custom-shader-effect-texture/tsconfig.json new file mode 100644 index 00000000..3893d2a3 --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "rootDir": ".", + "types": [], + "lib": ["ES2022", "DOM"], + "outDir": "dist-tsc", + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "sourceMap": true, + "declaration": true, + "experimentalDecorators": true, + + // Type Checking / Syntax Rules + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "composite": true, + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": ["node_modules", "./**/*.test.ts"], + "references": [ + { + "path": "./tsconfig.cfg.json" + } + ], +} + diff --git a/docs/example-projects/custom-shader-effect-texture/vite.config.ts b/docs/example-projects/custom-shader-effect-texture/vite.config.ts new file mode 100644 index 00000000..2e2acacc --- /dev/null +++ b/docs/example-projects/custom-shader-effect-texture/vite.config.ts @@ -0,0 +1,75 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from 'vite'; +import * as path from 'path'; + +/** + * Targeting ES2019 gets us at least to WPE 2.28 + * + * Despite setting the target in different places in the Vite config below + * this does not seem to have an effect on the output when running Vite in + * development mode (`pnpm start`). In order to properly test on embedded devices + * that require the es2019 target, you must run `pnpm run build` and then serve the + * production build via `pnpm run preview`. + * + * See the following for any updates on this: + * https://github.com/vitejs/vite/issues/13756#issuecomment-1751085158 + */ +const prodTarget = 'es2019'; + +/** + * esbuild target for development mode + * + * Must be at least ES2020 to `import.meta.glob` to work. Even though this target + * mainly affects the output for the Vite dev server, it still affects how the + * `import.meta.glob` is transpiled in the production output. + */ +const devTarget = 'es2020'; + +/** + * Vite Config + */ +export default defineConfig(({ command, mode }) => { + return { + worker: { + format: 'es', + }, + esbuild: { + target: devTarget, + }, + optimizeDeps: { + esbuildOptions: { + target: devTarget, + }, + }, + build: { + target: prodTarget, + minify: false, + sourcemap: true, + outDir: path.resolve(__dirname, 'dist'), + }, + server: { + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }, + }, + }; +}); diff --git a/examples/README.md b/examples/README.md index 51a27361..93e5db01 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,6 +44,8 @@ pnpm watch - `overlay` (boolean, default: "true") - Whether or not to show the text overlay in the bottom-right corner that displays the current test and driver being used. +- `monitor` (boolean, default: "false") + - Whether or not to show the Texture Memory Monitor overlay. - `resolution` (number, default: 720) - Resolution (height) of to render the test at (in logical pixels) - `fps` (boolean, default: "false") diff --git a/examples/common/AppCoreExtension.ts b/examples/common/AppCoreExtension.ts deleted file mode 100644 index 480877a1..00000000 --- a/examples/common/AppCoreExtension.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CoreExtension, - WebTrFontFace, - type Stage, - SdfTrFontFace, - type FontMetrics, -} from '@lightningjs/renderer/core'; - -export default class AppCoreExtension extends CoreExtension { - override async run(stage: Stage) { - stage.fontManager.addFontFace( - new WebTrFontFace({ - fontFamily: 'NotoSans', - descriptors: {}, - fontUrl: '/fonts/NotoSans-Regular.ttf', - metrics: { - ascender: 1069, - descender: -293, - lineGap: 0, - unitsPerEm: 1000, - }, - }), - ); - - stage.fontManager.addFontFace( - new WebTrFontFace({ - fontFamily: 'Ubuntu', - descriptors: {}, - fontUrl: '/fonts/Ubuntu-Regular.ttf', - metrics: { - ascender: 776, - descender: -185, - lineGap: 56, - unitsPerEm: 1000, - }, - }), - ); - - stage.fontManager.addFontFace( - new WebTrFontFace({ - fontFamily: 'Ubuntu-No-Metrics', - descriptors: {}, - fontUrl: '/fonts/Ubuntu-Regular.ttf', - }), - ); - - const ubuntuModifiedMetrics: FontMetrics = { - ascender: 850, - descender: -250, - lineGap: 60, - unitsPerEm: 1000, - }; - - stage.fontManager.addFontFace( - new WebTrFontFace({ - fontFamily: 'Ubuntu-Modified-Metrics', - descriptors: {}, - fontUrl: '/fonts/Ubuntu-Regular.ttf', - metrics: ubuntuModifiedMetrics, - }), - ); - - if (stage.renderer.mode === 'webgl') { - stage.fontManager.addFontFace( - new SdfTrFontFace('ssdf', { - fontFamily: 'NotoSans', - descriptors: {}, - atlasUrl: '/fonts/NotoSans-Regular.ssdf.png', - atlasDataUrl: '/fonts/NotoSans-Regular.ssdf.json', - stage, - metrics: { - ascender: 1000, - descender: -200, - lineGap: 0, - unitsPerEm: 1000, - }, - }), - ); - - stage.fontManager.addFontFace( - new SdfTrFontFace('msdf', { - fontFamily: 'Ubuntu', - descriptors: {}, - atlasUrl: '/fonts/Ubuntu-Regular.msdf.png', - atlasDataUrl: '/fonts/Ubuntu-Regular.msdf.json', - stage, - // Instead of suppling `metrics` this font will rely on the ones - // encoded in the json file under `lightningMetrics`. - }), - ); - - stage.fontManager.addFontFace( - new SdfTrFontFace('msdf', { - fontFamily: 'Ubuntu-Modified-Metrics', - descriptors: {}, - atlasUrl: '/fonts/Ubuntu-Regular.msdf.png', - atlasDataUrl: '/fonts/Ubuntu-Regular.msdf.json', - stage, - metrics: ubuntuModifiedMetrics, - }), - ); - - stage.fontManager.addFontFace( - new SdfTrFontFace('ssdf', { - fontFamily: 'Ubuntu-ssdf', - descriptors: {}, - atlasUrl: '/fonts/Ubuntu-Regular.ssdf.png', - atlasDataUrl: '/fonts/Ubuntu-Regular.ssdf.json', - stage, - metrics: { - ascender: 776, - descender: -185, - lineGap: 56, - unitsPerEm: 1000, - }, - }), - ); - } - } -} diff --git a/examples/common/Character.ts b/examples/common/Character.ts index a84a765b..ee35dce6 100644 --- a/examples/common/Character.ts +++ b/examples/common/Character.ts @@ -18,11 +18,10 @@ */ import type { - SpecificTextureRef, INode, - INodeWritableProps, + INodeProps, RendererMain, - TextureRef, + TextureMap, } from '@lightningjs/renderer'; import { assertTruthy } from '@lightningjs/renderer/utils'; @@ -31,12 +30,11 @@ export class Character { curIntervalAnimation: ReturnType | null = null; direction!: 'left' | 'right'; // Set in setState state!: 'idle' | 'walk' | 'run' | 'jump'; // Set in setState - leftFrames: TextureRef[] = []; constructor( - private props: Partial, + private props: Partial, private renderer: RendererMain, - private rightFrames: SpecificTextureRef<'SubTexture'>[], + private rightFrames: InstanceType[], ) { this.node = renderer.createNode({ x: props.x, @@ -47,11 +45,6 @@ export class Character { parent: renderer.root, zIndex: props.zIndex, }); - this.leftFrames = rightFrames.map((frame) => { - return renderer.createTexture('SubTexture', frame.props, { - flipX: true, - }); - }); assertTruthy(this.node); this.setState('right', 'idle'); } @@ -88,8 +81,8 @@ export class Character { intervalMs: number, ) { let curI = iStart; - const frameArr = direction === 'left' ? this.leftFrames : this.rightFrames; - if (iEnd + 1 > frameArr.length || iStart < 0) { + const flipX = direction === 'left' ? true : false; + if (iEnd + 1 > this.rightFrames.length || iStart < 0) { throw new Error('Animation out of bounds'); } if (this.curIntervalAnimation) { @@ -97,7 +90,8 @@ export class Character { } const nextFrame = () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.node.texture = frameArr[curI]!; + this.node.texture = this.rightFrames[curI]!; + this.node.textureOptions.flipX = flipX; curI++; if (curI > iEnd) { curI = iStart; diff --git a/examples/common/Component.ts b/examples/common/Component.ts index 8b02d499..3990dcca 100644 --- a/examples/common/Component.ts +++ b/examples/common/Component.ts @@ -17,19 +17,12 @@ * limitations under the License. */ -import type { - INode, - INodeWritableProps, - RendererMain, -} from '@lightningjs/renderer'; +import type { INode, INodeProps, RendererMain } from '@lightningjs/renderer'; export class Component { readonly node: INode; - constructor( - readonly renderer: RendererMain, - nodeProps: Partial, - ) { + constructor(readonly renderer: RendererMain, nodeProps: Partial) { this.node = renderer.createNode({ ...nodeProps, }); diff --git a/examples/common/ExampleSettings.ts b/examples/common/ExampleSettings.ts index 755bd00d..a2ef140c 100644 --- a/examples/common/ExampleSettings.ts +++ b/examples/common/ExampleSettings.ts @@ -18,6 +18,7 @@ */ import type { INode, RendererMain } from '@lightningjs/renderer'; +import type { MemMonitor } from './MemMonitor.js'; /** * Keep in sync with `visual-regression/src/index.ts` @@ -51,10 +52,6 @@ export interface ExampleSettings { * Renderer instance */ renderer: RendererMain; - /** - * Core Driver being used by the test. - */ - driverName: 'main' | 'threadx'; /** * The HTML Element that the Renderer's canvas is a child of */ @@ -87,4 +84,8 @@ export interface ExampleSettings { * This method will be a no-op if the test is not run in automation mode. */ snapshot(options?: SnapshotOptions): Promise; + /** + * The MemMonitor instance for the test (if enabled) + */ + memMonitor: MemMonitor | null; } diff --git a/examples/common/MemMonitor.ts b/examples/common/MemMonitor.ts new file mode 100644 index 00000000..03b62076 --- /dev/null +++ b/examples/common/MemMonitor.ts @@ -0,0 +1,267 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + INode, + INodeProps, + ITextNode, + RendererMain, +} from '@lightningjs/renderer'; +import { Component } from './Component.js'; + +const MARGIN = 20; +const BAR_WIDTH = 20; +const BAR_HEIGHT = 300; +const INFO_TEXT_SIZE = 20; +const INFO_TEXT_LINEHEIGHT = INFO_TEXT_SIZE * 1.2; + +function bytesToMb(bytes: number) { + return (bytes / 1024 / 1024).toFixed(2); +} + +export interface MemMonitorProps extends Partial { + interval?: number; +} + +export class MemMonitor extends Component { + // private memTextNode: ITextNode; + private bar: INode; + private renderableMemBar: INode; + private memUsedBar: INode; + private criticalText: ITextNode; + private criticalTick: INode; + private targetText: ITextNode; + private targetTick: INode; + private criticalInfoText: ITextNode; + private targetInfoText: ITextNode; + private memUsedText: ITextNode; + private renderableMemUsedText: ITextNode; + private cacheInfoText: ITextNode; + private intervalHandle: NodeJS.Timeout | null = null; + private _interval = 0; + + constructor(renderer: RendererMain, props: MemMonitorProps) { + super(renderer, props); + + this.interval = props.interval || 500; + this.node.color = 0xffffffaa; + this.node.width = 400; + this.node.height = BAR_HEIGHT + MARGIN * 2; + + this.bar = renderer.createNode({ + x: this.node.width - BAR_WIDTH - MARGIN, + y: MARGIN, + width: BAR_WIDTH, + height: BAR_HEIGHT, + parent: this.node, + color: 0x00000000, + }); + + this.memUsedBar = renderer.createNode({ + x: 0, + y: 0, + width: BAR_WIDTH, + height: 0, + parent: this.bar, + color: 0x0000ffff, + }); + + this.renderableMemBar = renderer.createNode({ + x: 0, + y: 0, + width: BAR_WIDTH, + height: 0, + parent: this.bar, + color: 0xff00ffff, + }); + + // Bar Frame + renderer.createNode({ + width: BAR_WIDTH, + height: BAR_HEIGHT, + rtt: true, + shader: renderer.createShader('DynamicShader', { + effects: [ + { + name: 'e1', + type: 'border', + props: { + color: 0x000000cc, + width: 4, + }, + }, + ], + }), + parent: this.bar, + }); + + this.criticalText = renderer.createTextNode({ + x: -15, + y: 0, + text: 'Critical', + fontFamily: 'Ubuntu', + parent: this.bar, + fontSize: 20, + color: 0xff0000ff, + mountX: 1, + mountY: 0.5, + }); + + this.criticalTick = renderer.createNode({ + x: BAR_WIDTH / 2, + y: 0, + width: BAR_WIDTH * 2, + height: 2, + parent: this.bar, + color: 0xff0000ff, + mount: 0.5, + }); + + this.targetText = renderer.createTextNode({ + x: -15, + y: 0, + text: 'Target', + fontFamily: 'Ubuntu', + parent: this.bar, + fontSize: 20, + color: 0x000000ff, + mountX: 1, + mountY: 0.5, + }); + + this.targetTick = renderer.createNode({ + x: BAR_WIDTH / 2, + y: 0, + width: BAR_WIDTH * 2, + height: 2, + parent: this.bar, + color: 0x000000ff, + mount: 0.5, + }); + + const numLines = 9; + const infoTextY = + this.node.height - MARGIN - INFO_TEXT_LINEHEIGHT * numLines; + + this.criticalInfoText = renderer.createTextNode({ + x: MARGIN, + y: infoTextY, + text: '', + fontFamily: 'Ubuntu', + parent: this.node, + fontSize: INFO_TEXT_SIZE, + lineHeight: INFO_TEXT_LINEHEIGHT, + color: 0xff0000ff, + }); + + this.targetInfoText = renderer.createTextNode({ + x: MARGIN, + y: infoTextY + INFO_TEXT_LINEHEIGHT, + text: '', + fontFamily: 'Ubuntu', + parent: this.node, + fontSize: INFO_TEXT_SIZE, + lineHeight: INFO_TEXT_LINEHEIGHT, + color: 0x000000ff, + }); + + this.memUsedText = renderer.createTextNode({ + x: MARGIN, + y: infoTextY + INFO_TEXT_LINEHEIGHT * 2, + text: '', + fontFamily: 'Ubuntu', + parent: this.node, + fontSize: INFO_TEXT_SIZE, + lineHeight: INFO_TEXT_LINEHEIGHT, + color: 0x0000ffff, + }); + + this.renderableMemUsedText = renderer.createTextNode({ + x: MARGIN, + y: infoTextY + INFO_TEXT_LINEHEIGHT * 5, + text: '', + fontFamily: 'Ubuntu', + parent: this.node, + fontSize: INFO_TEXT_SIZE, + lineHeight: INFO_TEXT_LINEHEIGHT, + color: 0xff00ffff, + }); + + this.cacheInfoText = renderer.createTextNode({ + x: MARGIN, + y: infoTextY + INFO_TEXT_LINEHEIGHT * 8, + text: '', + fontFamily: 'Ubuntu', + parent: this.node, + fontSize: INFO_TEXT_SIZE, + lineHeight: INFO_TEXT_LINEHEIGHT, + color: 0x000000ff, + }); + + const payload = this.renderer.stage.txMemManager.getMemoryInfo(); + const { criticalThreshold, targetThreshold } = payload; + const targetFraction = targetThreshold / criticalThreshold; + this.targetTick.y = BAR_HEIGHT - BAR_HEIGHT * targetFraction; + this.targetText.y = this.targetTick.y; + this.targetInfoText.text = `Target: ${bytesToMb(targetThreshold)} mb (${( + targetFraction * 100 + ).toFixed(1)}%)`; + this.criticalInfoText.text = `Critical: ${bytesToMb(criticalThreshold)} mb`; + + this.update(); + } + + update() { + const payload = this.renderer.stage.txMemManager.getMemoryInfo(); + const { criticalThreshold, memUsed, renderableMemUsed } = payload; + const renderableMemoryFraction = renderableMemUsed / criticalThreshold; + const memUsedFraction = memUsed / criticalThreshold; + this.memUsedBar.height = BAR_HEIGHT * memUsedFraction; + this.renderableMemBar.height = BAR_HEIGHT * renderableMemoryFraction; + this.renderableMemBar.y = BAR_HEIGHT - this.renderableMemBar.height; + this.memUsedBar.y = BAR_HEIGHT - this.memUsedBar.height; + this.memUsedText.text = ` +Textures Loaded +- Size: ${bytesToMb(memUsed)} mb (${(memUsedFraction * 100).toFixed(1)}%) +- Count: ${payload.loadedTextures} +`.trim(); + this.renderableMemUsedText.text = ` +Renderable Loaded +- ${bytesToMb(renderableMemUsed)} mb (${( + renderableMemoryFraction * 100 + ).toFixed(1)}%) +- Count: ${payload.renderableTexturesLoaded} +`.trim(); + this.cacheInfoText.text = `Cache Size: ${this.renderer.stage.txManager.keyCache.size}`; + } + + get interval() { + return this._interval; + } + + set interval(interval) { + this._interval = interval; + if (this.intervalHandle) { + clearInterval(this.intervalHandle); + } + this.intervalHandle = setInterval(() => { + this.update(); + }, this._interval); + } +} diff --git a/examples/common/PageContainer.ts b/examples/common/PageContainer.ts index 5bb78d71..2165d6a2 100644 --- a/examples/common/PageContainer.ts +++ b/examples/common/PageContainer.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import type { INode, ITextNode, RendererMain } from '@lightningjs/renderer'; +import type { INode, ITextNode } from '@lightningjs/renderer'; import { Component } from './Component.js'; import { loadStorage, saveStorage } from '../common/LocalStorage.js'; import type { ExampleSettings } from './ExampleSettings.js'; diff --git a/examples/common/constructTestRow.ts b/examples/common/constructTestRow.ts index a814fd93..d5e88526 100644 --- a/examples/common/constructTestRow.ts +++ b/examples/common/constructTestRow.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import type { INode, RendererMain } from '../../dist/exports/main-api.js'; +import type { INode, RendererMain } from '@lightningjs/renderer'; import { waitForLoadedDimensions } from './utils.js'; const CONTAINER_SIZE = 200; diff --git a/examples/common/installFonts.ts b/examples/common/installFonts.ts new file mode 100644 index 00000000..a5dbf617 --- /dev/null +++ b/examples/common/installFonts.ts @@ -0,0 +1,136 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + WebTrFontFace, + type Stage, + SdfTrFontFace, + type FontMetrics, +} from '@lightningjs/renderer'; + +export function installFonts(stage: Stage) { + stage.fontManager.addFontFace( + new WebTrFontFace({ + fontFamily: 'NotoSans', + descriptors: {}, + fontUrl: '/fonts/NotoSans-Regular.ttf', + metrics: { + ascender: 1069, + descender: -293, + lineGap: 0, + unitsPerEm: 1000, + }, + }), + ); + + stage.fontManager.addFontFace( + new WebTrFontFace({ + fontFamily: 'Ubuntu', + descriptors: {}, + fontUrl: '/fonts/Ubuntu-Regular.ttf', + metrics: { + ascender: 776, + descender: -185, + lineGap: 56, + unitsPerEm: 1000, + }, + }), + ); + + stage.fontManager.addFontFace( + new WebTrFontFace({ + fontFamily: 'Ubuntu-No-Metrics', + descriptors: {}, + fontUrl: '/fonts/Ubuntu-Regular.ttf', + }), + ); + + const ubuntuModifiedMetrics: FontMetrics = { + ascender: 850, + descender: -250, + lineGap: 60, + unitsPerEm: 1000, + }; + + stage.fontManager.addFontFace( + new WebTrFontFace({ + fontFamily: 'Ubuntu-Modified-Metrics', + descriptors: {}, + fontUrl: '/fonts/Ubuntu-Regular.ttf', + metrics: ubuntuModifiedMetrics, + }), + ); + + if (stage.renderer.mode === 'webgl') { + stage.fontManager.addFontFace( + new SdfTrFontFace('ssdf', { + fontFamily: 'NotoSans', + descriptors: {}, + atlasUrl: '/fonts/NotoSans-Regular.ssdf.png', + atlasDataUrl: '/fonts/NotoSans-Regular.ssdf.json', + stage, + metrics: { + ascender: 1000, + descender: -200, + lineGap: 0, + unitsPerEm: 1000, + }, + }), + ); + + stage.fontManager.addFontFace( + new SdfTrFontFace('msdf', { + fontFamily: 'Ubuntu', + descriptors: {}, + atlasUrl: '/fonts/Ubuntu-Regular.msdf.png', + atlasDataUrl: '/fonts/Ubuntu-Regular.msdf.json', + stage, + // Instead of suppling `metrics` this font will rely on the ones + // encoded in the json file under `lightningMetrics`. + }), + ); + + stage.fontManager.addFontFace( + new SdfTrFontFace('msdf', { + fontFamily: 'Ubuntu-Modified-Metrics', + descriptors: {}, + atlasUrl: '/fonts/Ubuntu-Regular.msdf.png', + atlasDataUrl: '/fonts/Ubuntu-Regular.msdf.json', + stage, + metrics: ubuntuModifiedMetrics, + }), + ); + + stage.fontManager.addFontFace( + new SdfTrFontFace('ssdf', { + fontFamily: 'Ubuntu-ssdf', + descriptors: {}, + atlasUrl: '/fonts/Ubuntu-Regular.ssdf.png', + atlasDataUrl: '/fonts/Ubuntu-Regular.ssdf.json', + stage, + metrics: { + ascender: 776, + descender: -185, + lineGap: 56, + unitsPerEm: 1000, + }, + }), + ); + } +} diff --git a/examples/common/paginateTestRows.ts b/examples/common/paginateTestRows.ts index d9847306..a2eb6eb4 100644 --- a/examples/common/paginateTestRows.ts +++ b/examples/common/paginateTestRows.ts @@ -18,8 +18,8 @@ */ import type { INode } from '@lightningjs/renderer'; -import { PageContainer } from './PageContainer.js'; import { assertTruthy } from '@lightningjs/renderer/utils'; +import { PageContainer } from './PageContainer.js'; const HEADER_FONT_SIZE = 30; const PADDING = 20; diff --git a/examples/common/utils.ts b/examples/common/utils.ts index ca307708..1586674f 100644 --- a/examples/common/utils.ts +++ b/examples/common/utils.ts @@ -19,9 +19,9 @@ import type { Dimensions, - ITextNode, - INode, NodeTextLoadedPayload, + INode, + ITextNode, } from '@lightningjs/renderer'; export async function waitForLoadedDimensions( diff --git a/examples/index.ts b/examples/index.ts index 120d7508..6a9ffab3 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -18,23 +18,20 @@ */ import { - MainCoreDriver, RendererMain, - ThreadXCoreDriver, - type ICoreDriver, type NodeLoadedPayload, type RendererMainSettings, type FpsUpdatePayload, } from '@lightningjs/renderer'; import { assertTruthy } from '@lightningjs/renderer/utils'; import * as mt19937 from '@stdlib/random-base-mt19937'; -import coreWorkerUrl from './common/CoreWorker.js?importChunkUrl'; -import coreExtensionModuleUrl from './common/AppCoreExtension.js?importChunkUrl'; import type { ExampleSettings, SnapshotOptions, } from './common/ExampleSettings.js'; import { StatTracker } from './common/StatTracker.js'; +import { installFonts } from './common/installFonts.js'; +import { MemMonitor } from './common/MemMonitor.js'; interface TestModule { default: (settings: ExampleSettings) => Promise; @@ -75,6 +72,7 @@ const defaultPhysicalPixelRatio = 1; */ const test = urlParams.get('test') || (automation ? '*' : 'test'); const showOverlay = urlParams.get('overlay') !== 'false'; + const showMemMonitor = urlParams.get('monitor') === 'true'; const logFps = urlParams.get('fps') === 'true'; const enableContextSpy = urlParams.get('contextSpy') === 'true'; const perfMultiplier = Number(urlParams.get('multiplier')) || 1; @@ -84,26 +82,18 @@ const defaultPhysicalPixelRatio = 1; Number(urlParams.get('ppr')) || defaultPhysicalPixelRatio; const logicalPixelRatio = resolution / appHeight; - let driverName = urlParams.get('driver'); - if (driverName !== 'main' && driverName !== 'threadx') { - driverName = 'main'; - } - let renderMode = urlParams.get('renderMode'); - if ( - driverName === 'threadx' || - (renderMode !== 'webgl' && renderMode !== 'canvas') - ) { + if (renderMode !== 'webgl' && renderMode !== 'canvas') { renderMode = 'webgl'; } if (!automation) { await runTest( test, - driverName, renderMode, urlParams, showOverlay, + showMemMonitor, logicalPixelRatio, physicalPixelRatio, logFps, @@ -114,17 +104,17 @@ const defaultPhysicalPixelRatio = 1; return; } assertTruthy(automation); - await runAutomation(driverName, renderMode, test, logFps); + await runAutomation(renderMode, test, logFps); })().catch((err) => { console.error(err); }); async function runTest( test: string, - driverName: string, renderMode: string, urlParams: URLSearchParams, showOverlay: boolean, + showMemMonitor: boolean, logicalPixelRatio: number, physicalPixelRatio: number, logFps: boolean, @@ -146,7 +136,6 @@ async function runTest( : {}; const { renderer, appElement } = await initRenderer( - driverName, renderMode, logFps, enableContextSpy, @@ -156,10 +145,12 @@ async function runTest( customSettings, ); + let testRoot = renderer.root; + if (showOverlay) { const overlayText = renderer.createTextNode({ color: 0xff0000ff, - text: `Test: ${test} | Driver: ${driverName}`, + text: `Test: ${test}`, zIndex: 99999, parent: renderer.root, fontSize: 50, @@ -173,18 +164,41 @@ async function runTest( ); } + let memMonitor: MemMonitor | null = null; + if (showMemMonitor) { + memMonitor = new MemMonitor(renderer, { + mount: 1, + x: renderer.settings.appWidth - 20, + y: renderer.settings.appHeight - 100, + parent: renderer.root, + zIndex: 99999, + }); + } + + if (showOverlay || showMemMonitor) { + // If we're showing the overlay text or mem monitor, create a new root node + // for the test content so it doesn't interfere with the overlay. + testRoot = renderer.createNode({ + parent: renderer.root, + x: renderer.root.x, + y: renderer.root.y, + width: renderer.settings.appWidth, + height: renderer.settings.appHeight - 100, + color: 0x00000000, + }); + } + const exampleSettings: ExampleSettings = { testName: test, renderer, - driverName: driverName as 'main' | 'threadx', appElement, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - testRoot: renderer.root!, + testRoot, automation: false, perfMultiplier: perfMultiplier, snapshot: async () => { // No-op }, + memMonitor, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call @@ -192,7 +206,6 @@ async function runTest( } async function initRenderer( - driverName: string, renderMode: string, logFps: boolean, enableContextSpy: boolean, @@ -201,16 +214,6 @@ async function initRenderer( enableInspector: boolean, customSettings?: Partial, ) { - let driver: ICoreDriver | null = null; - - if (driverName === 'main') { - driver = new MainCoreDriver(); - } else { - driver = new ThreadXCoreDriver({ - coreWorkerUrl, - }); - } - const renderer = new RendererMain( { appWidth, @@ -219,7 +222,6 @@ async function initRenderer( deviceLogicalPixelRatio: logicalPixelRatio, devicePhysicalPixelRatio: physicalPixelRatio, clearColor: 0x00000000, - coreExtensionModule: coreExtensionModuleUrl, fpsUpdateInterval: logFps ? 1000 : 0, enableContextSpy, enableInspector, @@ -227,8 +229,8 @@ async function initRenderer( ...customSettings, }, 'app', - driver, ); + installFonts(renderer.stage); /** * Sample data captured @@ -313,8 +315,6 @@ async function initRenderer( }, ); - await renderer.init(); - const appElement = document.querySelector('#app'); assertTruthy(appElement instanceof HTMLDivElement); @@ -330,14 +330,12 @@ function wildcardMatch(string: string, wildcardString: string) { } async function runAutomation( - driverName: string, renderMode: string, filter: string | null, logFps: boolean, ) { const logicalPixelRatio = defaultResolution / appHeight; const { renderer, appElement } = await initRenderer( - driverName, renderMode, logFps, false, @@ -383,7 +381,6 @@ async function runAutomation( testName, renderer, testRoot, - driverName: driverName as 'main' | 'threadx', appElement, automation: true, perfMultiplier: 1, @@ -421,6 +418,7 @@ async function runAutomation( ); } }, + memMonitor: null, }; await automation(exampleSettings); testRoot.parent = null; diff --git a/examples/package.json b/examples/package.json index 64fde16b..26426e37 100644 --- a/examples/package.json +++ b/examples/package.json @@ -24,8 +24,7 @@ "@stdlib/random-base-mt19937": "^0.2.1" }, "devDependencies": { - "@lightningjs/vite-plugin-import-chunk-url": "^0.3.0", - "vite": "^4.4.9" + "vite": "^5.0.0" }, "engines": { "npm": "please-use-pnpm" diff --git a/examples/tests/clipping.ts b/examples/tests/clipping.ts index 576e01d3..eb99d9f3 100644 --- a/examples/tests/clipping.ts +++ b/examples/tests/clipping.ts @@ -22,7 +22,7 @@ import { paginateTestRows } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; import { waitForLoadedDimensions } from '../common/utils.js'; import { deg2Rad } from '@lightningjs/renderer/utils'; -import type { INodeWritableProps } from '@lightningjs/renderer'; +import type { INodeProps } from '@lightningjs/renderer'; import robotImg from '../assets/robot/robot.png'; export async function automation(settings: ExampleSettings) { @@ -492,7 +492,7 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x00ff00ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const clippingParentProps = { mount: 0.5, @@ -502,14 +502,14 @@ export default async function test(settings: ExampleSettings) { height: SQUARE_SIZE / 2, clipping: true, // rotation: Math.PI / 4 - } satisfies Partial; + } satisfies Partial; const clippingChildProps = { width: SQUARE_SIZE, height: SQUARE_SIZE, mount: 0.5, src: robotImg, - } satisfies Partial; + } satisfies Partial; const container = renderer.createNode({ ...containerProps, @@ -632,7 +632,7 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x00ff00ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const clippingParentProps = { mount: 0.5, @@ -641,14 +641,14 @@ export default async function test(settings: ExampleSettings) { width: SQUARE_SIZE / 2, height: SQUARE_SIZE / 2, clipping: true, - } satisfies Partial; + } satisfies Partial; const clippingChildProps = { width: SQUARE_SIZE, height: SQUARE_SIZE, mount: 0.5, src: robotImg, - } satisfies Partial; + } satisfies Partial; const container = renderer.createNode({ ...containerProps, diff --git a/examples/tests/render-bounds.ts b/examples/tests/render-bounds.ts index 7c88e2e0..6065c5d5 100644 --- a/examples/tests/render-bounds.ts +++ b/examples/tests/render-bounds.ts @@ -1,6 +1,5 @@ -import type { IAnimationController } from '../../dist/exports/main-api.js'; +import type { IAnimationController } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; -import test from './alpha-blending.js'; export default async function ({ renderer, testRoot }: ExampleSettings) { const degToRad = (deg: number) => { diff --git a/examples/tests/resize-mode.ts b/examples/tests/resize-mode.ts index ed0ca47d..118884f4 100644 --- a/examples/tests/resize-mode.ts +++ b/examples/tests/resize-mode.ts @@ -20,7 +20,7 @@ import testscreenImg from '../assets/testscreen.png'; import testscreenRImg from '../assets/testscreen_rotated.png'; import type { ExampleSettings } from '../common/ExampleSettings.js'; -import type { INodeWritableProps } from '@lightningjs/renderer'; +import type { INodeProps } from '@lightningjs/renderer'; import { paginateTestRows } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; import { deg2Rad } from '../../dist/src/utils.js'; @@ -53,17 +53,16 @@ export default async function test(settings: ExampleSettings) { x: curX, width: SQUARE_SIZE, height: SQUARE_SIZE - 300, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenImg }, - { - resizeMode: { - type: 'cover', - clipY: [0, 0.5, 1][i], - clipX: [0, 0.5, 1][i], - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenImg, + }), + textureOptions: { + resizeMode: { + type: 'cover', + clipY: [0, 0.5, 1][i], + clipX: [0, 0.5, 1][i], }, - ), + }, parent: rowNode, }); curX += SQUARE_SIZE + PADDING; @@ -84,17 +83,16 @@ export default async function test(settings: ExampleSettings) { x: curX, width: SQUARE_SIZE, height: SQUARE_SIZE - 200, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenImg }, - { - resizeMode: { - type: 'cover', - clipY: [0, 0.5, 1][i], - clipX: [0, 0.5, 1][i], - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenImg, + }), + textureOptions: { + resizeMode: { + type: 'cover', + clipY: [0, 0.5, 1][i], + clipX: [0, 0.5, 1][i], }, - ), + }, parent: rowNode, }); curX += SQUARE_SIZE + PADDING; @@ -115,17 +113,16 @@ export default async function test(settings: ExampleSettings) { x: curX, width: SQUARE_SIZE, height: SQUARE_SIZE - 300, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenRImg }, - { - resizeMode: { - type: 'cover', - clipY: [0, 0.5, 1][i], - clipX: [0, 0.5, 1][i], - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenRImg, + }), + textureOptions: { + resizeMode: { + type: 'cover', + clipY: [0, 0.5, 1][i], + clipX: [0, 0.5, 1][i], }, - ), + }, parent: rowNode, }); curX += SQUARE_SIZE + PADDING; @@ -146,17 +143,16 @@ export default async function test(settings: ExampleSettings) { x: curX, width: SQUARE_SIZE - 400, height: SQUARE_SIZE - 100, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenRImg }, - { - resizeMode: { - type: 'cover', - clipX: [0, 0.25, 0.5, 0.75, 1][i], - clipY: [0, 0.25, 0.5, 0.75, 1][i], - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenRImg, + }), + textureOptions: { + resizeMode: { + type: 'cover', + clipX: [0, 0.25, 0.5, 0.75, 1][i], + clipY: [0, 0.25, 0.5, 0.75, 1][i], }, - ), + }, parent: rowNode, }); curX += SQUARE_SIZE + PADDING - 330; @@ -177,22 +173,21 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x333333ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const textureNodeProps = { width: containerProps.width, height: containerProps.height, clipping: true, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenImg }, - { - resizeMode: { - type: 'contain', - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenImg, + }), + textureOptions: { + resizeMode: { + type: 'contain', }, - ), - } satisfies Partial; + }, + } satisfies Partial; const container1 = renderer.createNode({ ...containerProps, @@ -221,22 +216,21 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x333333ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const textureNodeProps = { width: containerProps.width, height: containerProps.height, clipping: true, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenImg }, - { - resizeMode: { - type: 'contain', - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenImg, + }), + textureOptions: { + resizeMode: { + type: 'contain', }, - ), - } satisfies Partial; + }, + } satisfies Partial; const container1 = renderer.createNode({ ...containerProps, @@ -263,22 +257,21 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x333333ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const textureNodeProps = { width: containerProps.width, height: containerProps.height, clipping: true, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenRImg }, - { - resizeMode: { - type: 'contain', - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenRImg, + }), + textureOptions: { + resizeMode: { + type: 'contain', }, - ), - } satisfies Partial; + }, + } satisfies Partial; const container1 = renderer.createNode({ ...containerProps, @@ -322,22 +315,21 @@ export default async function test(settings: ExampleSettings) { parent: rowNode, color: 0x333333ff, clipping: true, - } satisfies Partial; + } satisfies Partial; const textureNodeProps = { width: containerProps.width, height: containerProps.height, clipping: true, - texture: renderer.createTexture( - 'ImageTexture', - { src: testscreenRImg }, - { - resizeMode: { - type: 'contain', - }, + texture: renderer.createTexture('ImageTexture', { + src: testscreenRImg, + }), + textureOptions: { + resizeMode: { + type: 'contain', }, - ), - } satisfies Partial; + }, + } satisfies Partial; const container1 = renderer.createNode({ ...containerProps, diff --git a/examples/tests/rtt-dimension.ts b/examples/tests/rtt-dimension.ts index 02a36bf2..6965174e 100644 --- a/examples/tests/rtt-dimension.ts +++ b/examples/tests/rtt-dimension.ts @@ -185,6 +185,10 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { width: 100, height: 100, texture: nestedRTTNode1.texture, + // Flip every other one of them + textureOptions: { + flipY: i % 2 === 1, + }, }); } diff --git a/examples/tests/scaling.ts b/examples/tests/scaling.ts index 692cc54e..0afecfe4 100644 --- a/examples/tests/scaling.ts +++ b/examples/tests/scaling.ts @@ -16,11 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import type { INodeProps } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; -import type { INodeWritableProps } from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; import robotImg from '../assets/robot/robot.png'; @@ -51,7 +50,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -88,7 +87,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -125,7 +124,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -165,7 +164,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -202,7 +201,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -239,7 +238,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -279,7 +278,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -316,7 +315,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ @@ -353,7 +352,7 @@ export default async function test(settings: ExampleSettings) { width: 100, height: 100, src: robotImg, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createNode({ diff --git a/examples/tests/shader-animation.ts b/examples/tests/shader-animation.ts new file mode 100644 index 00000000..8109677f --- /dev/null +++ b/examples/tests/shader-animation.ts @@ -0,0 +1,154 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export async function automation(settings: ExampleSettings) { + // Snapshot single page + await test(settings); + // await settings.snapshot(); +} + +export default async function test({ + renderer, + testRoot, + snapshot, +}: ExampleSettings) { + const degToRad = (deg: number) => { + return (Math.PI / 180) * deg; + }; + + const nodeSize = { + width: 300, + height: 300, + }; + + const t1 = renderer.createNode({ + ...nodeSize, + x: 90, + y: 90, + color: 0xff0000ff, + shader: renderer.createShader('RoundedRectangle', { + radius: 100, + }), + parent: testRoot, + }); + + const t1Radius = renderer.createTextNode({ + mountX: 1, + x: testRoot.width - 90, + y: 90, + fontSize: 40, + fontFamily: 'Ubuntu', + text: 'radius: 100', + parent: testRoot, + color: 0xffffffff, + }); + + const t2 = renderer.createNode({ + ...nodeSize, + x: 90, + y: 540, + color: 0x00ff00ff, + shader: renderer.createDynamicShader([ + renderer.createEffect( + 'radius', + { + radius: 0, + }, + 'r1', + ), + renderer.createEffect( + 'border', + { + color: 0xff00ffff, + width: 10, + }, + 'e1', + ), + ]), + parent: testRoot, + }); + + const t2Radius = renderer.createTextNode({ + mountX: 1, + x: testRoot.width - 90, + y: 540, + fontSize: 40, + fontFamily: 'Ubuntu', + text: 'radius: 0', + parent: testRoot, + color: 0xffffffff, + }); + + const t2Border = renderer.createTextNode({ + mountX: 1, + x: testRoot.width - 90, + y: 590, + fontSize: 40, + fontFamily: 'Ubuntu', + text: 'border: 10', + parent: testRoot, + color: 0xffffffff, + }); + + await snapshot({ name: 'startup' }); + + const shaderAnimation = t1.animate( + { + x: 1140, + shaderProps: { + radius: 150, + }, + }, + { + duration: 500, + }, + ); + shaderAnimation.start(); + await shaderAnimation.waitUntilStopped(); + t1Radius.text = 'radius: ' + t1.shader.props.radius!.toString(); + + await snapshot({ name: 'animation1' }); + + const shaderAnimation2 = t2.animate( + { + x: 1140, + shaderProps: { + r1: { + radius: 150, + }, + e1: { + width: 50, + }, + }, + }, + { + duration: 500, + }, + ); + + shaderAnimation2.start(); + await shaderAnimation2.waitUntilStopped(); + t2Radius.text = 'radius: ' + t2.shader.props.r1.radius!.toString(); + t2Border.text = 'border: ' + t2.shader.props.e1.width!.toString(); + + await snapshot({ name: 'animation2' }); + console.log('ready!'); +} diff --git a/examples/tests/stress-multi-level-clipping.ts b/examples/tests/stress-multi-level-clipping.ts index 42085cc0..41becb0c 100644 --- a/examples/tests/stress-multi-level-clipping.ts +++ b/examples/tests/stress-multi-level-clipping.ts @@ -18,7 +18,6 @@ */ import { type INode } from '@lightningjs/renderer'; -import logo from '../assets/lightning.png'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import robotImg from '../assets/robot/robot.png'; diff --git a/examples/tests/stress-single-level-text.ts b/examples/tests/stress-single-level-text.ts index a299dc4d..cf5bdd35 100644 --- a/examples/tests/stress-single-level-text.ts +++ b/examples/tests/stress-single-level-text.ts @@ -18,7 +18,6 @@ */ import { type INode } from '@lightningjs/renderer'; -import logo from '../assets/lightning.png'; import type { ExampleSettings } from '../common/ExampleSettings.js'; const randomIntBetween = (from: number, to: number) => diff --git a/examples/tests/stress-single-level.ts b/examples/tests/stress-single-level.ts index 56fd968c..f488dfed 100644 --- a/examples/tests/stress-single-level.ts +++ b/examples/tests/stress-single-level.ts @@ -30,7 +30,7 @@ export default async function ({ perfMultiplier, }: ExampleSettings) { // create 100 nodes - const numOuterNodes = 100 * perfMultiplier; + const numOuterNodes = 1 * perfMultiplier; const nodes: INode[] = []; const bg = renderer.createNode({ @@ -46,10 +46,12 @@ export default async function ({ height: 101, x: Math.random() * 1920, y: Math.random() * 1080, - src: logo, + // src: logo, parent: bg, }); + node.src = logo; + nodes.push(node); } diff --git a/examples/tests/test.ts b/examples/tests/test.ts index 15844363..28a51058 100644 --- a/examples/tests/test.ts +++ b/examples/tests/test.ts @@ -169,17 +169,12 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { }); setInterval(() => { - shaft.texture = renderer.createTexture( - 'NoiseTexture', - { - width: 210, - height: renderer.settings.appHeight, - cacheId: Math.floor(Math.random() * 100000), - }, - { - preload: true, - }, - ); + shaft.texture = renderer.createTexture('NoiseTexture', { + width: 210, + height: renderer.settings.appHeight, + cacheId: Math.floor(Math.random() * 100000), + }); + shaft.textureOptions.preload = true; }, 1000); // setTimeout required for ThreadX right now because the emit() that sends diff --git a/examples/tests/text-alpha.ts b/examples/tests/text-alpha.ts index 4347e72e..7f4e0eb1 100644 --- a/examples/tests/text-alpha.ts +++ b/examples/tests/text-alpha.ts @@ -17,14 +17,14 @@ * limitations under the License. */ +import type { + INodeProps, + ITextNodeProps, + RendererMain, +} from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; -import type { - INodeWritableProps, - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; const containerSize = 100; @@ -38,7 +38,7 @@ const NODE_PROPS = { fontFamily: 'Ubuntu', textRendererOverride: 'sdf', fontSize: 50, -} satisfies Partial; +} satisfies Partial; export async function automation(settings: ExampleSettings) { // Snapshot all the pages @@ -73,7 +73,7 @@ function generateAlphaTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ renderer.createTextNode({ @@ -103,7 +103,7 @@ function generateAlphaTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ createContainedTextNode(renderer, textRenderer, { @@ -130,7 +130,7 @@ function generateAlphaTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ renderer.createTextNode({ @@ -159,7 +159,7 @@ function generateAlphaTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ createContainedTextNode(renderer, textRenderer, { @@ -188,7 +188,7 @@ function generateAlphaTest( function createContainedTextNode( renderer: RendererMain, textRenderer: 'canvas' | 'sdf', - containerProps: Partial, + containerProps: Partial, ) { const container = renderer.createNode({ width: containerSize, diff --git a/examples/tests/text-baseline.ts b/examples/tests/text-baseline.ts index ba752705..529e9b0a 100644 --- a/examples/tests/text-baseline.ts +++ b/examples/tests/text-baseline.ts @@ -17,14 +17,11 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; import { waitForLoadedDimensions } from '../common/utils.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; export async function automation(settings: ExampleSettings) { @@ -57,7 +54,7 @@ const NODE_PROPS = { textRendererOverride: 'sdf', fontSize: 50, lineHeight: 70, -} satisfies Partial; +} satisfies Partial; function generateBaselineTest( renderer: RendererMain, @@ -72,10 +69,11 @@ function generateBaselineTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, + parent: renderer.root, }); const dimensions = await waitForLoadedDimensions(baselineNode); diff --git a/examples/tests/text-canvas.ts b/examples/tests/text-canvas.ts new file mode 100644 index 00000000..d3cee531 --- /dev/null +++ b/examples/tests/text-canvas.ts @@ -0,0 +1,156 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +const Colors = { + Black: 0x000000ff, + Red: 0xff0000ff, + Green: 0x00ff00ff, + Blue: 0x0000ffff, + Magenta: 0xff00ffff, + Gray: 0x7f7f7fff, + White: 0xffffffff, +}; + +const randomIntBetween = (from: number, to: number) => + Math.floor(Math.random() * (to - from + 1) + from); + +/** + * Tests that Single-Channel Signed Distance Field (SSDF) fonts are rendered + * correctly. + * + * Text that is thinner than the certified snapshot may indicate that the + * SSDF font atlas texture was premultiplied before being uploaded to the GPU. + * + * @param settings + * @returns + */ +export default async function test(settings: ExampleSettings) { + const { renderer, testRoot } = settings; + + // Set a smaller snapshot area + // testRoot.width = 200; + // testRoot.height = 200; + // testRoot.color = 0xffffffff; + + const nodes: any[] = []; + + const renderNode = (t: string) => { + const node = renderer.createTextNode({ + x: Math.random() * 1900, + y: Math.random() * 1080, + text: 'CANVAS ' + t, + fontFamily: 'sans-serif', + parent: testRoot, + fontSize: 80, + }); + + nodes.push(node); + + // pick random color from Colors + node.color = + Object.values(Colors)[ + randomIntBetween(0, Object.keys(Colors).length - 1) + ] || 0xff0000ff; + }; + + const spawn = (amount = 100) => { + for (let i = 0; i < amount; i++) { + renderNode(i.toString()); + } + }; + + const despawn = (amount = 100) => { + for (let i = 0; i < amount; i++) { + const node = nodes.pop(); + node.destroy(); + } + }; + + const move = () => { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + node.x = randomIntBetween(0, 1600); + node.y = randomIntBetween(0, 880); + } + }; + + const newColor = () => { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + node.color = + Object.values(Colors)[ + randomIntBetween(0, Object.keys(Colors).length - 1) + ] || 0x000000ff; + } + }; + + let animating = false; + const animate = () => { + animating = !animating; + + const animateNode = (node: any) => { + nodes.forEach((node) => { + node + .animate( + { + x: randomIntBetween(20, 1740), + y: randomIntBetween(20, 900), + rotation: Math.random() * Math.PI, + }, + { + duration: 3000, + easing: 'ease-out', + }, + ) + .start(); + }); + }; + + const animateRun = () => { + if (animating) { + for (let i = 0; i < nodes.length; i++) { + animateNode(nodes[i]); + } + setTimeout(animateRun, 3050); + } + }; + + animateRun(); + }; + + window.addEventListener('keydown', (event) => { + if (event.key === 'ArrowUp') { + spawn(); + } else if (event.key === 'ArrowDown') { + despawn(); + } else if (event.key === 'ArrowLeft') { + move(); + } else if (event.key === 'ArrowRight') { + move(); + } else if (event.key === '1') { + newColor(); + } else if (event.key === ' ') { + animate(); + } + }); + + spawn(); +} diff --git a/examples/tests/text-events.ts b/examples/tests/text-events.ts index 9532ae1b..884aa03f 100644 --- a/examples/tests/text-events.ts +++ b/examples/tests/text-events.ts @@ -33,13 +33,9 @@ const FONT_SIZE = 60; const BUTTON_PADDING = 10; const BEGIN_Y = HEADER_SIZE; -export default async function ({ - renderer, - driverName, - testRoot, -}: ExampleSettings) { +export default async function ({ renderer, testRoot }: ExampleSettings) { const header = renderer.createTextNode({ - text: `Text Event Test (${driverName})`, + text: `Text Event Test`, fontSize: HEADER_SIZE, offsetY: -5, parent: testRoot, diff --git a/examples/tests/text-line-height.ts b/examples/tests/text-line-height.ts index c2aad222..12f41035 100644 --- a/examples/tests/text-line-height.ts +++ b/examples/tests/text-line-height.ts @@ -17,14 +17,10 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; -import { waitForLoadedDimensions } from '../common/utils.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; export async function automation(settings: ExampleSettings) { @@ -57,7 +53,7 @@ const NODE_PROPS = { fontFamily: 'Ubuntu', textRendererOverride: 'sdf', fontSize: 50, -} satisfies Partial; +} satisfies Partial; function generateLineHeightTest( renderer: RendererMain, @@ -72,7 +68,7 @@ function generateLineHeightTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text-max-lines.ts b/examples/tests/text-max-lines.ts index 4f579134..5c1a161f 100644 --- a/examples/tests/text-max-lines.ts +++ b/examples/tests/text-max-lines.ts @@ -17,13 +17,10 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; import { getLoremIpsum } from '../common/LoremIpsum.js'; @@ -59,7 +56,7 @@ const BASE_NODE_PROPS = { fontSize: 20, lineHeight: 28, contain: 'width', -} satisfies Partial; +} satisfies Partial; function generateMaxLinesTest( renderer: RendererMain, @@ -73,7 +70,7 @@ function generateMaxLinesTest( ...BASE_NODE_PROPS, text: 'Line1 Line1_Line1_Line1\nLine2 Line2____Line2\nLine 3\nLine 4', textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, @@ -129,7 +126,7 @@ function generateMaxLinesTest( ...BASE_NODE_PROPS, text: getLoremIpsum(100), textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text-offscreen-move.ts b/examples/tests/text-offscreen-move.ts index dd0eaa09..51039889 100644 --- a/examples/tests/text-offscreen-move.ts +++ b/examples/tests/text-offscreen-move.ts @@ -19,7 +19,7 @@ import type { INode, - ITextNodeWritableProps, + ITextNodeProps, RendererMain, } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; @@ -68,12 +68,12 @@ const commonTextProps = { fontFamily: 'Ubuntu', textRendererOverride: 'canvas', fontSize: 50, -} satisfies Partial; +} satisfies Partial; function createTestCase( renderer: RendererMain, textRenderer: 'canvas' | 'sdf', - contain: ITextNodeWritableProps['contain'], + contain: ITextNodeProps['contain'], ) { return async function (page: INode) { const subheader = renderer.createTextNode({ diff --git a/examples/tests/text-overflow-suffix.ts b/examples/tests/text-overflow-suffix.ts index 415179b4..2cdbbd4e 100644 --- a/examples/tests/text-overflow-suffix.ts +++ b/examples/tests/text-overflow-suffix.ts @@ -17,13 +17,10 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; import { getLoremIpsum } from '../common/LoremIpsum.js'; @@ -60,7 +57,7 @@ const NODE_PROPS = { fontSize: 20, lineHeight: 28, contain: 'both', -} satisfies Partial; +} satisfies Partial; function generateOverflowSuffixTest( renderer: RendererMain, @@ -73,7 +70,7 @@ function generateOverflowSuffixTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text-rotation.ts b/examples/tests/text-rotation.ts index 8422069f..460eea34 100644 --- a/examples/tests/text-rotation.ts +++ b/examples/tests/text-rotation.ts @@ -17,14 +17,11 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; import { waitForLoadedDimensions } from '../common/utils.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; export async function automation(settings: ExampleSettings) { @@ -56,7 +53,7 @@ const NODE_PROPS = { fontFamily: 'Ubuntu', textRendererOverride: 'sdf', fontSize: 50, -} satisfies Partial; +} satisfies Partial; function generateRotationTest( renderer: RendererMain, @@ -69,7 +66,7 @@ function generateRotationTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, @@ -120,7 +117,7 @@ function generateRotationTest( x: 100, y: 100, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ renderer.createTextNode({ @@ -154,7 +151,7 @@ function generateRotationTest( ...NODE_PROPS, mount: 1, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text-scaling.ts b/examples/tests/text-scaling.ts index 0314e230..aac310fb 100644 --- a/examples/tests/text-scaling.ts +++ b/examples/tests/text-scaling.ts @@ -17,14 +17,11 @@ * limitations under the License. */ +import type { ITextNodeProps, RendererMain } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; import { PageContainer } from '../common/PageContainer.js'; import { waitForLoadedDimensions } from '../common/utils.js'; -import type { - ITextNodeWritableProps, - RendererMain, -} from '../../dist/exports/main-api.js'; import { constructTestRow } from '../common/constructTestRow.js'; export async function automation(settings: ExampleSettings) { @@ -61,7 +58,7 @@ const NODE_PROPS = { fontFamily: 'Ubuntu', textRendererOverride: 'sdf', fontSize: 50, -} satisfies Partial; +} satisfies Partial; function generateScalingTest( renderer: RendererMain, @@ -75,7 +72,7 @@ function generateScalingTest( const nodeProps = { ...NODE_PROPS, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, @@ -145,7 +142,7 @@ function generateScalingTest( x: 100, y: 100, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, @@ -200,7 +197,7 @@ function generateScalingTest( ...NODE_PROPS, mount: 1, textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text-vertical-align.ts b/examples/tests/text-vertical-align.ts index 13face40..4b8337f6 100644 --- a/examples/tests/text-vertical-align.ts +++ b/examples/tests/text-vertical-align.ts @@ -17,14 +17,14 @@ * limitations under the License. */ -import type { ExampleSettings } from '../common/ExampleSettings.js'; -import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; -import { PageContainer } from '../common/PageContainer.js'; import type { ITextNode, - ITextNodeWritableProps, + ITextNodeProps, RendererMain, -} from '../../dist/exports/main-api.js'; +} from '@lightningjs/renderer'; +import type { ExampleSettings } from '../common/ExampleSettings.js'; +import { paginateTestRows, type TestRow } from '../common/paginateTestRows.js'; +import { PageContainer } from '../common/PageContainer.js'; import { constructTestRow } from '../common/constructTestRow.js'; export async function automation(settings: ExampleSettings) { @@ -55,7 +55,7 @@ const NODE_PROPS = { textRendererOverride: 'sdf', fontSize: 50, lineHeight: 70, -} satisfies Partial; +} satisfies Partial; const CONTAINER_SIZE = 200; @@ -94,7 +94,7 @@ function generateVerticalAlignTest( ...NODE_PROPS, text: 'txyz', textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, @@ -129,7 +129,7 @@ function generateVerticalAlignTest( ...NODE_PROPS, text: 'abcd\ntxyz', textRendererOverride: textRenderer, - } satisfies Partial; + } satisfies Partial; const baselineNode = renderer.createTextNode({ ...nodeProps, diff --git a/examples/tests/text.ts b/examples/tests/text.ts index 197d3a97..174fe588 100644 --- a/examples/tests/text.ts +++ b/examples/tests/text.ts @@ -18,7 +18,7 @@ */ import { - type ITextNodeWritableProps, + type ITextNodeProps, type TextRendererMap, type TrFontFaceMap, type NodeLoadedEventHandler, @@ -35,7 +35,7 @@ const FONT_FAMILY = 'Ubuntu'; const HEADER_SIZE = 45; const FONT_SIZE = 40; -const initialMutableProps: Partial = { +const initialMutableProps: Partial = { x: 0, y: 0, fontFamily: FONT_FAMILY, @@ -57,7 +57,7 @@ export const Colors = { }; interface LocalStorageData { - mutableProps: Partial; + mutableProps: Partial; curMode: number; moveStep: number; curColorIdx: number; @@ -87,7 +87,7 @@ export default async function ({ const text = getLoremIpsum(); - const initialProps: Partial = { + const initialProps: Partial = { ...(savedState?.mutableProps || initialMutableProps), fontFamily: FONT_FAMILY, contain: 'both', diff --git a/examples/tests/reference-texture-memory.ts b/examples/tests/texture-cleanup-critical.ts similarity index 60% rename from examples/tests/reference-texture-memory.ts rename to examples/tests/texture-cleanup-critical.ts index 89b16ff0..f04d0d6b 100644 --- a/examples/tests/reference-texture-memory.ts +++ b/examples/tests/texture-cleanup-critical.ts @@ -16,26 +16,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { RendererMainSettings } from '../../dist/exports/main-api.js'; +import type { RendererMainSettings } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; -export function customSettings( - urlParams: URLSearchParams, -): Partial { - const finalizationRegistry = urlParams.get('finalizationRegistry') === 'true'; +export function customSettings(): Partial { return { - textureCleanupOptions: { - textureCleanupAgeThreadholdMs: 6000, - textureCleanupIntervalMs: 1000, + textureMemory: { + criticalThreshold: 100 * 1024 ** 2, + targetThresholdLevel: 0.25, + debugLogging: true, }, - // Disable the threshold-based memory manager. This will allow this test to - // focus only on the reference-based memory manager. - txMemByteThreshold: 0, - experimental_FinalizationRegistryTextureUsageTracker: finalizationRegistry, }; } -export default async function ({ renderer, testRoot }: ExampleSettings) { +export default async function ({ + renderer, + testRoot, + memMonitor, +}: ExampleSettings) { + // Make the memory monitor update fast + if (memMonitor) { + memMonitor.interval = 10; + } const screen = renderer.createNode({ x: 0, y: 0, @@ -48,7 +50,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { renderer.createTextNode({ x: 0, y: 0, - text: 'Reference-based Texture Memory Management Test', + text: 'Critical Texture Memory Cleanup Test', parent: screen, fontFamily: 'Ubuntu', fontSize: 60, @@ -59,7 +61,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 100, width: renderer.settings.appWidth, contain: 'width', - text: `This test will create and display a random NoiseTexture node every 10ms. + text: `This test will create and display a random NoiseTexture node every 10ms and never offer a moment for Idle Texture Cleanup. Only Critical Texture Cleanup will be triggered. See docs/ManualRegressionTests.md for more information. `, @@ -70,16 +72,11 @@ See docs/ManualRegressionTests.md for more information. // Create a new random texture every 10ms setInterval(() => { - screen.texture = renderer.createTexture( - 'NoiseTexture', - { - width: 500, - height: 500, - cacheId: Math.floor(Math.random() * 100000), - }, - { - preload: true, - }, - ); + screen.texture = renderer.createTexture('NoiseTexture', { + width: 500, + height: 500, + cacheId: Math.floor(Math.random() * 100000), + }); + screen.textureOptions.preload = true; }, 10); } diff --git a/examples/tests/threshold-texture-memory.ts b/examples/tests/texture-cleanup-idle.ts similarity index 74% rename from examples/tests/threshold-texture-memory.ts rename to examples/tests/texture-cleanup-idle.ts index ddb5c0da..8fd0efe6 100644 --- a/examples/tests/threshold-texture-memory.ts +++ b/examples/tests/texture-cleanup-idle.ts @@ -16,23 +16,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import type { INode, RendererMainSettings } from '@lightningjs/renderer'; import type { ExampleSettings } from '../common/ExampleSettings.js'; -export function customSettings( - urlParams: URLSearchParams, -): Partial { - const finalizationRegistry = urlParams.get('finalizationRegistry') === 'true'; - const txMemByteThreshold = urlParams.get('txMemByteThreshold'); +export function customSettings(): Partial { return { - textureCleanupOptions: { - textureCleanupAgeThreadholdMs: 6000, - textureCleanupIntervalMs: 1000, + textureMemory: { + cleanupInterval: 5000, + debugLogging: true, }, - txMemByteThreshold: txMemByteThreshold - ? Number(txMemByteThreshold) - : 100000000 /* 100MB */, - experimental_FinalizationRegistryTextureUsageTracker: finalizationRegistry, }; } @@ -61,7 +54,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { renderer.createTextNode({ x: 0, y: 0, - text: 'Threshold-based Texture Memory Management Test', + text: 'Idle Texture Memory Cleanup Test', parent: testRoot, fontFamily: 'Ubuntu', fontSize: 60, @@ -73,7 +66,9 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 100, width: renderer.settings.appWidth, contain: 'width', - text: `This test will create and display a grid of random NoiseTexture nodes and move them off of the bounds margin every 100ms. + text: `This test will create and display a grid of random NoiseTexture nodes and move them off of the bounds margin every second. + +The Texture Memory Manager should perform Idle Texture Cleanup roughly every 5 seconds. See docs/ManualRegressionTests.md for more information. `, @@ -103,22 +98,19 @@ See docs/ManualRegressionTests.md for more information. height: nodeHeight, parent: testRoot, color: randomColor(), - texture: renderer.createTexture( - 'NoiseTexture', - { - width: nodeWidth, - height: nodeHeight, - cacheId: Math.floor(Math.random() * 100000), - }, - { - preload: true, - }, - ), + texture: renderer.createTexture('NoiseTexture', { + width: nodeWidth, + height: nodeHeight, + cacheId: Math.floor(Math.random() * 100000), + }), + textureOptions: { + preload: true, + }, }); curNodes.push(node); } } - await delay(100); + await delay(1000); // Move all nodes offscreen beyond the bounds margin for (const node of curNodes) { node.x = -screenWidth * 2; diff --git a/examples/tests/texture-factory.ts b/examples/tests/texture-factory.ts index f621a38f..14e90b7d 100644 --- a/examples/tests/texture-factory.ts +++ b/examples/tests/texture-factory.ts @@ -25,11 +25,7 @@ export async function automation(settings: ExampleSettings) { await settings.snapshot(); } -export default async function test({ - renderer, - driverName, - testRoot, -}: ExampleSettings) { +export default async function test({ renderer, testRoot }: ExampleSettings) { const randomColor = () => { const randomInt = Math.floor(Math.random() * Math.pow(2, 32)); const hexString = randomInt.toString(16).padStart(8, '0'); @@ -43,7 +39,7 @@ export default async function test({ const FONT_SIZE = 45; renderer.createTextNode({ - text: `Texture Factory Test (${driverName})`, + text: `Texture Factory Test`, fontSize: FONT_SIZE, offsetY: -5, parent: testRoot, diff --git a/examples/tests/textures.ts b/examples/tests/textures.ts index d72aab8f..60009545 100644 --- a/examples/tests/textures.ts +++ b/examples/tests/textures.ts @@ -33,17 +33,13 @@ export async function automation(settings: ExampleSettings) { await settings.snapshot(); } -export default async function test({ - renderer, - driverName, - testRoot, -}: ExampleSettings) { +export default async function test({ renderer, testRoot }: ExampleSettings) { const FONT_SIZE = 45; const BEGIN_Y = FONT_SIZE; const header = renderer.createTextNode({ fontFamily: 'Ubuntu', - text: `Texture Test (${driverName})`, + text: `Texture Test`, fontSize: FONT_SIZE, parent: testRoot, }); diff --git a/examples/tests/viewport-events-canvas.ts b/examples/tests/viewport-events-canvas.ts new file mode 100644 index 00000000..e9b6858b --- /dev/null +++ b/examples/tests/viewport-events-canvas.ts @@ -0,0 +1,208 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export default async function ({ renderer, testRoot }: ExampleSettings) { + const instructionText = renderer.createTextNode({ + text: 'Press space to start animation, arrow keys to move, enter to reset', + fontSize: 30, + x: 10, + y: 960, + fontFamily: 'Ubuntu-ssdf', + parent: testRoot, + }); + + const redStatus = renderer.createTextNode({ + text: 'Red Status: ', + fontSize: 30, + x: 10, + y: 50, + fontFamily: 'Ubuntu-ssdf', + parent: testRoot, + }); + + const blueStatus = renderer.createTextNode({ + text: 'Blue Status: ', + fontSize: 30, + x: 10, + y: 10, + fontFamily: 'Ubuntu-ssdf', + parent: testRoot, + }); + + const boundaryRect = renderer.createNode({ + x: 1920 / 2 - (1920 * 0.75) / 2, + y: 1080 / 2 - (1080 * 0.75) / 2, + width: 1440, + height: 810, + color: 0x000000ff, + clipping: true, + parent: testRoot, + }); + + const redText = renderer.createTextNode({ + x: 500, + y: 305, + alpha: 1, + width: 200, + height: 200, + color: 0xff0000ff, + pivot: 0, + text: 'red', + fontSize: 80, + fontFamily: 'sans-serif', + parent: boundaryRect, + }); + + redText.on('outOfBounds', () => { + console.log('red text out of bounds'); + redStatus.text = 'Red Status: text out of bounds'; + redStatus.color = 0xff0000ff; + }); + + redText.on('inViewport', () => { + console.log('red text in view port'); + redStatus.text = 'Red Status: text in view port'; + redStatus.color = 0x00ff00ff; + }); + + redText.on('inBounds', () => { + console.log('red text inside render bounds'); + redStatus.text = 'Red Status: text in bounds'; + redStatus.color = 0xffff00ff; + }); + + const blueText = renderer.createTextNode({ + x: 1920 / 2 - 200, + y: 100, + alpha: 1, + width: 200, + height: 200, + color: 0x0000ffff, + pivot: 0, + text: 'blue', + fontSize: 80, + fontFamily: 'sans-serif', + parent: testRoot, + }); + + blueText.on('outOfBounds', () => { + console.log('blue text ouf ot bounds'); + blueStatus.text = 'Blue Status: blue text out of bounds'; + blueStatus.color = 0xff0000ff; + }); + + blueText.on('inViewport', () => { + console.log('blue text in view port'); + blueStatus.text = 'Blue Status: blue text in view port'; + blueStatus.color = 0x00ff00ff; + }); + + blueText.on('inBounds', () => { + console.log('blue text inside render bounds'); + blueStatus.text = 'Blue Status: blue text in bounds'; + blueStatus.color = 0xffff00ff; + }); + + let runAnimation = false; + const animate = async () => { + redText + .animate( + { + x: -500, + }, + { + duration: 4000, + }, + ) + .start(); + + await blueText + .animate( + { + x: -1200, + }, + { + duration: 4000, + }, + ) + .start() + .waitUntilStopped(); + + redText.x = 1920 + 400; + blueText.x = 1920 + 400; + + redText + .animate( + { + x: 520, + }, + { + duration: 4000, + }, + ) + .start(); + + await blueText + .animate( + { + x: 1920 / 2 - 200, + }, + { + duration: 4000, + }, + ) + .start() + .waitUntilStopped(); + + if (runAnimation) { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + setTimeout(animate, 2000); + } + }; + + const moveModifier = 10; + window.onkeydown = (e) => { + if (e.key === ' ') { + runAnimation = !runAnimation; + + if (runAnimation) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + animate(); + } + } + + if (e.key === 'ArrowRight') { + redText.x += moveModifier; + blueText.x += moveModifier; + } + + if (e.key === 'ArrowLeft') { + redText.x -= moveModifier; + blueText.x -= moveModifier; + } + + if (e.key === 'Enter') { + runAnimation = false; + redText.x = 520; + blueText.x = 1920 / 2 - 200; + } + }; +} diff --git a/examples/tests/viewport-events.ts b/examples/tests/viewport-events.ts new file mode 100644 index 00000000..19da04a7 --- /dev/null +++ b/examples/tests/viewport-events.ts @@ -0,0 +1,185 @@ +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export default async function ({ renderer, testRoot }: ExampleSettings) { + const degToRad = (deg: number) => { + return (Math.PI / 180) * deg; + }; + + const instructionText = renderer.createTextNode({ + text: 'Press space to start animation, arrow keys to move, enter to reset', + fontSize: 30, + x: 10, + y: 960, + parent: testRoot, + }); + + const redStatus = renderer.createTextNode({ + text: 'Red Status: ', + fontSize: 30, + x: 10, + y: 50, + parent: testRoot, + }); + + const blueStatus = renderer.createTextNode({ + text: 'Blue Status: ', + fontSize: 30, + x: 10, + y: 10, + parent: testRoot, + }); + + const boundaryRect = renderer.createNode({ + x: 1920 / 2 - (1920 * 0.75) / 2, + y: 1080 / 2 - (1080 * 0.75) / 2, + width: 1440, + height: 810, + color: 0x000000ff, + clipping: true, + parent: testRoot, + }); + + const redRect = renderer.createNode({ + // skipRender: true, + x: 520, + y: 305, + alpha: 1, + width: 200, + height: 200, + color: 0xff0000ff, + pivot: 0, + parent: boundaryRect, + }); + + redRect.on('outOfBounds', () => { + console.log('red rect out of bounds'); + redStatus.text = 'Red Status: rect out of bounds'; + redStatus.color = 0xff0000ff; + }); + + redRect.on('inViewport', () => { + console.log('red rect in view port'); + redStatus.text = 'Red Status: rect in view port'; + redStatus.color = 0x00ff00ff; + }); + + redRect.on('inBounds', () => { + console.log('red rect inside render bounds'); + redStatus.text = 'Red Status: rect in bounds'; + redStatus.color = 0xffff00ff; + }); + + const blueRect = renderer.createNode({ + x: 1920 / 2 - 200, + y: 100, + alpha: 1, + width: 200, + height: 200, + color: 0x0000ffff, + pivot: 0, + parent: testRoot, + }); + + blueRect.on('outOfBounds', () => { + console.log('blue rect ouf ot bounds'); + blueStatus.text = 'Blue Status: blue rect out of bounds'; + blueStatus.color = 0xff0000ff; + }); + + blueRect.on('inViewport', () => { + console.log('blue rect in view port'); + blueStatus.text = 'Blue Status: blue rect in view port'; + blueStatus.color = 0x00ff00ff; + }); + + blueRect.on('inBounds', () => { + console.log('blue rect inside render bounds'); + blueStatus.text = 'Blue Status: blue rect in bounds'; + blueStatus.color = 0xffff00ff; + }); + + let runAnimation = false; + const animate = async () => { + redRect + .animate( + { + x: -500, + }, + { + duration: 4000, + }, + ) + .start(); + + await blueRect + .animate( + { + x: -1200, + }, + { + duration: 4000, + }, + ) + .start() + .waitUntilStopped(); + + redRect.x = 1920 + 400; + blueRect.x = 1920 + 400; + + redRect + .animate( + { + x: 520, + }, + { + duration: 4000, + }, + ) + .start(); + + await blueRect + .animate( + { + x: 1920 / 2 - 200, + }, + { + duration: 4000, + }, + ) + .start() + .waitUntilStopped(); + + if (runAnimation) { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + setTimeout(animate, 2000); + } + }; + + const moveModifier = 10; + window.onkeydown = (e) => { + if (e.key === ' ') { + runAnimation = !runAnimation; + + if (runAnimation) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + animate(); + } + } + + if (e.key === 'ArrowRight') { + redRect.x += moveModifier; + blueRect.x += moveModifier; + } + + if (e.key === 'ArrowLeft') { + redRect.x -= moveModifier; + blueRect.x -= moveModifier; + } + + if (e.key === 'Enter') { + runAnimation = false; + redRect.x = 520; + blueRect.x = 1920 / 2 - 200; + } + }; +} diff --git a/examples/vite-env.d.ts b/examples/vite-env.d.ts index 72d7a860..77dd71a8 100644 --- a/examples/vite-env.d.ts +++ b/examples/vite-env.d.ts @@ -19,5 +19,3 @@ /// // This enables Vite's import.meta augmentations and possibly other things? -/// -// This installs 'import' types from the 'vite-plugin-import-chunk-url' plugin diff --git a/examples/vite.config.ts b/examples/vite.config.ts index 7b4888d3..b9698741 100644 --- a/examples/vite.config.ts +++ b/examples/vite.config.ts @@ -19,7 +19,6 @@ import { defineConfig } from 'vite'; import * as path from 'path'; -import { importChunkUrl } from '@lightningjs/vite-plugin-import-chunk-url'; /** * Targeting ES2019 gets us at least to WPE 2.28 @@ -47,9 +46,9 @@ const devTarget = 'es2020'; /** * Vite Config */ -export default defineConfig(({ command, mode, ssrBuild }) => { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default defineConfig(({ command, mode, isSsrBuild }) => { return { - plugins: [importChunkUrl()], worker: { format: 'es', }, diff --git a/exports/core-api.ts b/exports/core-api.ts deleted file mode 100644 index 34e7bca1..00000000 --- a/exports/core-api.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Lightning 3 Renderer Core API - * - * @remarks - * ``` - * import * from '@lightning/renderer/core'; - * ``` - * - * The Core API is used by developers to extend the capabilities of the Renderer - * by writing custom Shaders, Dynamic Shader Effects, Textures, Text Renderers, - * etc. - * - * Custom capabilities as well as fonts can be loaded via Core Extensions. - * - * A core extension module is structured like so: - * ```ts - * import { - * CoreExtension, - * WebTrFontFace, - * SdfTrFontFace, - * type Stage - * } from '@lightning/renderer/core'; - * - * export default class MyCoreExtension extends CoreExtension { - * async run(stage: Stage) { - * stage.fontManager.addFontFace( - * new WebTrFontFace('Ubuntu', {}, '/fonts/Ubuntu-Regular.ttf'), - * ); - * - * stage.fontManager.addFontFace( - * new SdfTrFontFace( - * 'Ubuntu', - * {}, - * 'msdf', - * stage, - * '/fonts/Ubuntu-Regular.msdf.png', - * '/fonts/Ubuntu-Regular.msdf.json', - * ), - * ); - * } - * } - * ``` - * - * And then imported and registered in the application's entry point - * using the `@lightningjs/vite-plugin-import-chunk-url` plugin: - * ```ts - * import coreExtensionModuleUrl from './MyCoreExtension.js?importChunkUrl'; - * - * // Set up driver, etc. - * - * // Initialize the Renderer - * const renderer = new RendererMain( - * { - * // Other Renderer Config... - * coreExtensionModule: coreExtensionModuleUrl, - * }, - * 'app', - * driver, - * ); - * ``` - * - * @module - */ - -// Shaders -export * from '../src/core/renderers/webgl/WebGlCoreShader.js'; -export * from '../src/core/renderers/webgl/shaders/effects/ShaderEffect.js'; - -// Textures -export * from '../src/core/textures/Texture.js'; - -// Text Rendering & Fonts -export * from '../src/core/text-rendering/renderers/TextRenderer.js'; -export * from '../src/core/text-rendering/renderers/CanvasTextRenderer.js'; -export * from '../src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js'; -export * from '../src/core/text-rendering/font-face-types/TrFontFace.js'; -export * from '../src/core/text-rendering/font-face-types/WebTrFontFace.js'; -export * from '../src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js'; - -// Core Extensions -export * from '../src/core/CoreExtension.js'; - -// Stage (type only for Core Extensions) -export type * from '../src/core/Stage.js'; diff --git a/exports/index.ts b/exports/index.ts index 449a2813..f59d4e2f 100644 --- a/exports/index.ts +++ b/exports/index.ts @@ -17,6 +17,71 @@ * limitations under the License. */ -export * from './main-api.js'; -export * from './core-api.js'; -export * from './utils.js'; +/** + * Lightning 3 Renderer API + * + * @remarks + * This module exports the API for the Lightning 3 Renderer. You + * can import the exports from this module like so: + * ```ts + * import { Renderer } from '@lightning/renderer'; + * ``` + * + * Generally developers/frameworks using the Renderer will use the Main API to + * render applications. + * + * Do not confuse the Main API with the Core API which is used to extend + * capabilities of the Renderer. The Main API code always runs from the main + * thread. + * + * @module + */ + +export * from '../src/main-api/INode.js'; +export * from '../src/main-api/Renderer.js'; +export * from '../src/main-api/ShaderController.js'; +export * from '../src/main-api/DynamicShaderController.js'; +export * from '../src/common/IAnimationController.js'; +export * from '../src/common/CommonTypes.js'; + +// Selected types exported from the Core Renderer that can be used in the +// context of the main API. +export { + CoreTextureManager, + type TextureMap, +} from '../src/core/CoreTextureManager.js'; +export type { MemoryInfo } from '../src/core/TextureMemoryManager.js'; +export type { ShaderMap, EffectMap } from '../src/core/CoreShaderManager.js'; +export type { TextRendererMap } from '../src/core/text-rendering/renderers/TextRenderer.js'; +export type { TrFontFaceMap } from '../src/core/text-rendering/font-face-types/TrFontFace.js'; +export type { AnimationSettings } from '../src/core/animations/CoreAnimation.js'; +export type { + EffectProps, + FadeOutEffectProps, + LinearGradientEffectProps, + RadialGradientEffectProps, + GrayscaleEffectProps, + GlitchEffectProps, + RadialProgressEffectProps, +} from '../src/core/CoreShaderManager.js'; +export type { WebGlCoreRenderer } from '../src/core/renderers/webgl/WebGlCoreRenderer.js'; +export type { WebGlCoreCtxTexture } from '../src/core/renderers/webgl/WebGlCoreCtxTexture.js'; + +// Shaders +export * from '../src/core/renderers/webgl/WebGlCoreShader.js'; +export * from '../src/core/renderers/webgl/shaders/effects/ShaderEffect.js'; +export type { ShaderProgramSources } from '../src/core/renderers/webgl/internal/ShaderUtils.js'; + +// Textures +export * from '../src/core/textures/Texture.js'; + +// Text Rendering & Fonts +export * from '../src/core/text-rendering/renderers/TextRenderer.js'; +export * from '../src/core/text-rendering/renderers/CanvasTextRenderer.js'; +export * from '../src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js'; +export * from '../src/core/text-rendering/font-face-types/TrFontFace.js'; +export * from '../src/core/text-rendering/font-face-types/WebTrFontFace.js'; +export * from '../src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js'; + +// Stage (type only for Core Extensions) +export type * from '../src/core/Stage.js'; diff --git a/exports/main-api.ts b/exports/main-api.ts deleted file mode 100644 index e1103907..00000000 --- a/exports/main-api.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Lightning 3 Renderer Main API - * - * @remarks - * This module exports the Main API for the Lightning 3 Renderer. You - * can import the exports from this module like so: - * ```ts - * import { RendererMain } from '@lightning/renderer'; - * ``` - * - * Generally developers/frameworks using the Renderer will use the Main API to - * render applications. - * - * Do not confuse the Main API with the Core API which is used to extend - * capabilities of the Renderer. The Main API code always runs from the main - * thread. - * - * @module - */ -export * from '../src/main-api/INode.js'; -export * from '../src/main-api/ICoreDriver.js'; -export * from '../src/main-api/RendererMain.js'; -export * from '../src/render-drivers/main/MainCoreDriver.js'; -export * from '../src/render-drivers/threadx/ThreadXCoreDriver.js'; -export * from '../src/common/IAnimationController.js'; -export * from '../src/common/CommonTypes.js'; - -// Selected types exported from the Core Renderer that can be used in the -// context of the main API. -export type { TextRendererMap } from '../src/core/text-rendering/renderers/TextRenderer.js'; -export type { TrFontFaceMap } from '../src/core/text-rendering/font-face-types/TrFontFace.js'; -export type { AnimationSettings } from '../src/core/animations/CoreAnimation.js'; -export type { - EffectMap, - EffectProps, - FadeOutEffectProps, - LinearGradientEffectProps, - RadialGradientEffectProps, - GrayscaleEffectProps, - GlitchEffectProps, - RadialProgressEffectProps, - HolePunchEffectProps, -} from '../src/core/CoreShaderManager.js'; diff --git a/exports/utils.ts b/exports/utils.ts index 87650fa2..3f743c4f 100644 --- a/exports/utils.ts +++ b/exports/utils.ts @@ -38,4 +38,5 @@ * @packageDocumentation */ export { assertTruthy, mergeColorAlpha, deg2Rad } from '../src/utils.js'; +export { getNormalizedRgbaComponents } from '../src/core/lib/utils.js'; export { EventEmitter } from '../src/common/EventEmitter.js'; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 85f6b7fa..00000000 --- a/package-lock.json +++ /dev/null @@ -1,6102 +0,0 @@ -{ - "name": "@lightningjs/renderer", - "version": "0.9.4", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@lightningjs/renderer", - "version": "0.9.4", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@lightningjs/threadx": "^0.3.4", - "@protobufjs/eventemitter": "^1.1.0", - "memize": "^2.1.0" - }, - "devDependencies": { - "@types/node": "^18.14.6", - "@typescript-eslint/eslint-plugin": "^5.55.0", - "@typescript-eslint/parser": "^5.55.0", - "concurrently": "^8.0.1", - "eslint": "^8.35.0", - "eslint-config-prettier": "^8.7.0", - "husky": "^8.0.3", - "lint-staged": "^13.2.0", - "prettier": "^2.8.4", - "typedoc": "^0.25.1", - "typescript": "^5.2.2", - "vitest": "^0.34.2" - }, - "engines": { - "node": ">= 20.9.0", - "npm": ">= 10.0.0", - "pnpm": ">= 8.9.2" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@lightningjs/threadx": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@lightningjs/threadx/-/threadx-0.3.4.tgz", - "integrity": "sha512-x9ylADrRxSrIczcMHgkNYpmnvoPvWKuasxmEf3dnixazpYJCw9HLoWNOi2UsD0U4oEL/tMQZA0sZbO7VVGMTag==" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@types/chai": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz", - "integrity": "sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==", - "dev": true - }, - "node_modules/@types/chai-subset": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.4.tgz", - "integrity": "sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==", - "dev": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", - "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vitest/expect": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", - "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", - "dev": true, - "dependencies": { - "@vitest/spy": "0.34.6", - "@vitest/utils": "0.34.6", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", - "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", - "dev": true, - "dependencies": { - "@vitest/utils": "0.34.6", - "p-limit": "^4.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/snapshot": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", - "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", - "dev": true, - "dependencies": { - "magic-string": "^0.30.1", - "pathe": "^1.1.1", - "pretty-format": "^29.5.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", - "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", - "dev": true, - "dependencies": { - "tinyspy": "^2.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", - "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", - "dev": true, - "dependencies": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dev": true, - "dependencies": { - "type-fest": "^1.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "date-fns": "^2.30.0", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": "^14.13.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true - }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lint-staged": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", - "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", - "dev": true, - "dependencies": { - "chalk": "5.3.0", - "commander": "11.0.0", - "debug": "4.3.4", - "execa": "7.2.0", - "lilconfig": "2.1.0", - "listr2": "6.6.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.1" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", - "dev": true, - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", - "dev": true, - "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/memize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.0.tgz", - "integrity": "sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", - "dev": true, - "dependencies": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "ufo": "^1.3.0" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" - } - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shiki": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", - "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", - "dev": true, - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "node_modules/std-env": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", - "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", - "dev": true - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "dependencies": { - "acorn": "^8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tinybench": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", - "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", - "dev": true - }, - "node_modules/tinypool": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", - "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedoc": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.2.tgz", - "integrity": "sha512-286F7BeATBiWe/qC4PCOCKlSTwfnsLbC/4cZ68oGBbvAqb9vV33quEOXx7q176OXotD+JdEerdQ1OZGJ818lnA==", - "dev": true, - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" - } - }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", - "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", - "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", - "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", - "dev": true, - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "mlly": "^1.4.0", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": ">=v14.18.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", - "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", - "dev": true, - "dependencies": { - "@types/chai": "^4.3.5", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.34.6", - "@vitest/runner": "0.34.6", - "@vitest/snapshot": "0.34.6", - "@vitest/spy": "0.34.6", - "@vitest/utils": "0.34.6", - "acorn": "^8.9.0", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", - "local-pkg": "^0.4.3", - "magic-string": "^0.30.1", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.3.3", - "strip-literal": "^1.0.1", - "tinybench": "^2.5.0", - "tinypool": "^0.7.0", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", - "vite-node": "0.34.6", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": ">=v14.18.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@vitest/browser": "*", - "@vitest/ui": "*", - "happy-dom": "*", - "jsdom": "*", - "playwright": "*", - "safaridriver": "*", - "webdriverio": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "playwright": { - "optional": true - }, - "safaridriver": { - "optional": true - }, - "webdriverio": { - "optional": true - } - } - }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", - "dev": true, - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@babel/runtime": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", - "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@lightningjs/threadx": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@lightningjs/threadx/-/threadx-0.3.4.tgz", - "integrity": "sha512-x9ylADrRxSrIczcMHgkNYpmnvoPvWKuasxmEf3dnixazpYJCw9HLoWNOi2UsD0U4oEL/tMQZA0sZbO7VVGMTag==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@types/chai": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz", - "integrity": "sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==", - "dev": true - }, - "@types/chai-subset": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.4.tgz", - "integrity": "sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==", - "dev": true, - "requires": { - "@types/chai": "*" - } - }, - "@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", - "dev": true - }, - "@types/node": { - "version": "18.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", - "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", - "dev": true - }, - "@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "@vitest/expect": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", - "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", - "dev": true, - "requires": { - "@vitest/spy": "0.34.6", - "@vitest/utils": "0.34.6", - "chai": "^4.3.10" - } - }, - "@vitest/runner": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", - "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", - "dev": true, - "requires": { - "@vitest/utils": "0.34.6", - "p-limit": "^4.0.0", - "pathe": "^1.1.1" - }, - "dependencies": { - "p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true - } - } - }, - "@vitest/snapshot": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", - "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", - "dev": true, - "requires": { - "magic-string": "^0.30.1", - "pathe": "^1.1.1", - "pretty-format": "^29.5.0" - } - }, - "@vitest/spy": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", - "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", - "dev": true, - "requires": { - "tinyspy": "^2.1.1" - } - }, - "@vitest/utils": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", - "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", - "dev": true, - "requires": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - } - }, - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dev": true, - "requires": { - "type-fest": "^1.0.2" - }, - "dependencies": { - "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "requires": { - "get-func-name": "^2.0.2" - } - }, - "cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "requires": { - "restore-cursor": "^4.0.0" - } - }, - "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", - "dev": true, - "requires": { - "chalk": "^4.1.2", - "date-fns": "^2.30.0", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.21.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true - }, - "execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true - }, - "husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - }, - "lint-staged": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz", - "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==", - "dev": true, - "requires": { - "chalk": "5.3.0", - "commander": "11.0.0", - "debug": "4.3.4", - "execa": "7.2.0", - "lilconfig": "2.1.0", - "listr2": "6.6.1", - "micromatch": "4.0.5", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.3.1" - }, - "dependencies": { - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true - } - } - }, - "listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", - "dev": true, - "requires": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" - } - }, - "local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", - "dev": true, - "requires": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true - }, - "memize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.0.tgz", - "integrity": "sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", - "dev": true, - "requires": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "ufo": "^1.3.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - }, - "dependencies": { - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true - }, - "pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "requires": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" - } - }, - "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - } - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true - }, - "shiki": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", - "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", - "dev": true, - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - } - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "std-env": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", - "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", - "dev": true - }, - "string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "requires": { - "acorn": "^8.10.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tinybench": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", - "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", - "dev": true - }, - "tinypool": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", - "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", - "dev": true - }, - "tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typedoc": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.2.tgz", - "integrity": "sha512-286F7BeATBiWe/qC4PCOCKlSTwfnsLbC/4cZ68oGBbvAqb9vV33quEOXx7q176OXotD+JdEerdQ1OZGJ818lnA==", - "dev": true, - "requires": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true - }, - "ufo": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", - "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - } - }, - "vite-node": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", - "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", - "dev": true, - "requires": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "mlly": "^1.4.0", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" - } - }, - "vitest": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", - "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", - "dev": true, - "requires": { - "@types/chai": "^4.3.5", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.34.6", - "@vitest/runner": "0.34.6", - "@vitest/snapshot": "0.34.6", - "@vitest/spy": "0.34.6", - "@vitest/utils": "0.34.6", - "acorn": "^8.9.0", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", - "local-pkg": "^0.4.3", - "magic-string": "^0.30.1", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.3.3", - "strip-literal": "^1.0.1", - "tinybench": "^2.5.0", - "tinypool": "^0.7.0", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", - "vite-node": "0.34.6", - "why-is-node-running": "^2.2.2" - } - }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", - "dev": true, - "requires": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/package.json b/package.json index fe45dcd9..6ba4db93 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,12 @@ { "name": "@lightningjs/renderer", - "version": "0.9.4", + "version": "1.0.0-rc.3", "description": "Lightning 3 Renderer", "type": "module", "main": "./dist/exports/index.js", "exports": { - ".": "./dist/exports/main-api.js", - "./core": "./dist/exports/core-api.js", - "./utils": "./dist/exports/utils.js", - "./workers/renderer": "./dist/src/render-drivers/threadx/worker/renderer.js" + ".": "./dist/exports/index.js", + "./utils": "./dist/exports/utils.js" }, "scripts": { "preinstall": "node scripts/please-use-pnpm.js", @@ -18,6 +16,7 @@ "build:docker": "docker build -t visual-regression .", "watch": "tsc --build --watch", "test": "vitest", + "coverage": "vitest run --coverage", "test:visual": "cd visual-regression && pnpm test:visual", "lint": "pnpm run lint:prettier && pnpm run lint:eslint", "lint:fix": "pnpm run lint:fix:prettier && pnpm run lint:fix:eslint", @@ -49,9 +48,10 @@ }, "homepage": "https://github.com/lightning-js/renderer#readme", "devDependencies": { - "@types/node": "^18.14.6", + "@types/node": "^20.0.0", "@typescript-eslint/eslint-plugin": "^5.55.0", "@typescript-eslint/parser": "^5.55.0", + "@vitest/coverage-v8": "^1.6.0", "concurrently": "^8.0.1", "eslint": "^8.35.0", "eslint-config-prettier": "^8.7.0", @@ -60,7 +60,8 @@ "prettier": "^2.8.4", "typedoc": "^0.25.1", "typescript": "^5.2.2", - "vitest": "^0.34.2" + "vitest": "^1.6.0", + "vitest-mock-extended": "^1.3.1" }, "dependencies": { "@lightningjs/threadx": "^0.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55e199c9..db1d3b9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,14 +19,17 @@ importers: version: 2.1.0 devDependencies: '@types/node': - specifier: ^18.14.6 - version: 18.18.6 + specifier: ^20.0.0 + version: 20.14.9 '@typescript-eslint/eslint-plugin': specifier: ^5.55.0 version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.52.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^5.55.0 version: 5.62.0(eslint@8.52.0)(typescript@5.2.2) + '@vitest/coverage-v8': + specifier: ^1.6.0 + version: 1.6.0(vitest@1.6.0) concurrently: specifier: ^8.0.1 version: 8.2.2 @@ -52,8 +55,11 @@ importers: specifier: ^5.2.2 version: 5.2.2 vitest: - specifier: ^0.34.2 - version: 0.34.6 + specifier: ^1.6.0 + version: 1.6.0(@types/node@20.14.9) + vitest-mock-extended: + specifier: ^1.3.1 + version: 1.3.1(typescript@5.2.2)(vitest@1.6.0) examples: dependencies: @@ -64,12 +70,9 @@ importers: specifier: ^0.2.1 version: 0.2.1 devDependencies: - '@lightningjs/vite-plugin-import-chunk-url': - specifier: ^0.3.0 - version: 0.3.0(vite@4.5.0) vite: - specifier: ^4.4.9 - version: 4.5.0(@types/node@18.18.6) + specifier: ^5.0.0 + version: 5.3.1(@types/node@20.14.9) visual-regression: dependencies: @@ -115,6 +118,32 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@babel/helper-string-parser@7.24.7: + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/parser@7.24.7: + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.7 + dev: true + /@babel/runtime@7.23.2: resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} engines: {node: '>=6.9.0'} @@ -122,8 +151,30 @@ packages: regenerator-runtime: 0.14.0 dev: true - /@esbuild/android-arm64@0.18.20: - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + /@babel/types@7.24.7: + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -131,8 +182,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.18.20: - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -140,8 +191,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.18.20: - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -149,8 +200,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.18.20: - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -158,8 +209,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.18.20: - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -167,8 +218,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.18.20: - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -176,8 +227,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.18.20: - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -185,8 +236,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.18.20: - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -194,8 +245,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.18.20: - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -203,8 +254,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.18.20: - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -212,8 +263,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.18.20: - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -221,8 +272,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.18.20: - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -230,8 +281,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.18.20: - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -239,8 +290,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.18.20: - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -248,8 +299,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.18.20: - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -257,8 +308,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.18.20: - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -266,8 +317,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.18.20: - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -275,8 +326,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.18.20: - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -284,8 +335,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.18.20: - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -293,8 +344,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.18.20: - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -302,8 +353,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.18.20: - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -311,8 +362,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.18.20: - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -377,6 +428,11 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -384,22 +440,40 @@ packages: '@sinclair/typebox': 0.27.8 dev: true + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true + /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@lightningjs/threadx@0.3.5: resolution: {integrity: sha512-aUIUfP/d3S0Vg/Ror7CLbqQR6qu5AAdGRnQ5RQuSQz4t9X2p8TomKD7ShRxI1/FnRR7om2MwKIe1Dix/Axdgpg==} dev: false - /@lightningjs/vite-plugin-import-chunk-url@0.3.0(vite@4.5.0): - resolution: {integrity: sha512-cozdB9gcPrnITWTymo525Vb+98EfsBOBQnrbzXI6rOFveshxc754eco0ffnuqyFUkN8pds+DwJrJ5saJDmJz/g==} - peerDependencies: - vite: ^4.4.9 - dependencies: - vite: 4.5.0(@types/node@18.18.6) - dev: true - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -425,6 +499,134 @@ packages: resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} dev: false + /@rollup/rollup-android-arm-eabi@4.18.0: + resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.18.0: + resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.18.0: + resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.18.0: + resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.18.0: + resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.18.0: + resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.18.0: + resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.18.0: + resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.18.0: + resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.18.0: + resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.18.0: + resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.18.0: + resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.18.0: + resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.18.0: + resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.18.0: + resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.18.0: + resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -1670,14 +1872,8 @@ packages: '@stdlib/utils-global': 0.2.1 dev: false - /@types/chai-subset@1.3.4: - resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} - dependencies: - '@types/chai': 4.3.9 - dev: true - - /@types/chai@4.3.9: - resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true /@types/fs-extra@11.0.4: @@ -1701,6 +1897,12 @@ packages: resolution: {integrity: sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==} dev: true + /@types/node@20.14.9: + resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/semver@7.5.4: resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} dev: true @@ -1853,40 +2055,64 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitest/expect@0.34.6: - resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + /@vitest/coverage-v8@1.6.0(vitest@1.6.0): + resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} + peerDependencies: + vitest: 1.6.0 + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.4 + istanbul-reports: 3.1.7 + magic-string: 0.30.5 + magicast: 0.3.4 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + vitest: 1.6.0(@types/node@20.14.9) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@1.6.0: + resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} dependencies: - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 chai: 4.3.10 dev: true - /@vitest/runner@0.34.6: - resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + /@vitest/runner@1.6.0: + resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} dependencies: - '@vitest/utils': 0.34.6 - p-limit: 4.0.0 + '@vitest/utils': 1.6.0 + p-limit: 5.0.0 pathe: 1.1.1 dev: true - /@vitest/snapshot@0.34.6: - resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + /@vitest/snapshot@1.6.0: + resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} dependencies: magic-string: 0.30.5 pathe: 1.1.1 pretty-format: 29.7.0 dev: true - /@vitest/spy@0.34.6: - resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + /@vitest/spy@1.6.0: + resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} dependencies: tinyspy: 2.2.0 dev: true - /@vitest/utils@0.34.6: - resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + /@vitest/utils@1.6.0: + resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} dependencies: diff-sequences: 29.6.3 + estree-walker: 3.0.3 loupe: 2.3.7 pretty-format: 29.7.0 dev: true @@ -1899,9 +2125,11 @@ packages: acorn: 8.10.0 dev: true - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + /acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.12.0 dev: true /acorn@8.10.0: @@ -1910,6 +2138,12 @@ packages: hasBin: true dev: true + /acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -2178,34 +2412,35 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true - /esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 dev: true /escalade@3.1.1: @@ -2327,6 +2562,12 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2364,7 +2605,6 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: false /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2479,7 +2719,6 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: false /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -2545,6 +2784,10 @@ packages: function-bind: 1.1.2 dev: false + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + /human-signals@4.3.1: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} @@ -2553,7 +2796,6 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: false /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} @@ -2590,8 +2832,9 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + /is-core-module@2.14.0: + resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 dev: false @@ -2634,6 +2877,43 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@5.0.4: + resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==} + engines: {node: '>=10'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + dev: true + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -2721,9 +3001,12 @@ packages: wrap-ansi: 8.1.0 dev: true - /local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} + dependencies: + mlly: 1.4.2 + pkg-types: 1.0.3 dev: true /locate-path@6.0.0: @@ -2776,6 +3059,21 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magicast@0.3.4: + resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + source-map-js: 1.2.0 + dev: true + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + /marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} @@ -2841,8 +3139,8 @@ packages: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true @@ -2899,9 +3197,9 @@ packages: yocto-queue: 0.1.0 dev: true - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} dependencies: yocto-queue: 1.0.0 dev: true @@ -2998,13 +3296,13 @@ packages: fsevents: 2.3.2 dev: true - /postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /prelude-ls@1.2.1: @@ -3057,7 +3355,7 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.13.1 + is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: false @@ -3086,11 +3384,29 @@ packages: glob: 7.2.3 dev: true - /rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} + /rollup@4.18.0: + resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + dependencies: + '@types/estree': 1.0.5 optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.0 + '@rollup/rollup-android-arm64': 4.18.0 + '@rollup/rollup-darwin-arm64': 4.18.0 + '@rollup/rollup-darwin-x64': 4.18.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 + '@rollup/rollup-linux-arm-musleabihf': 4.18.0 + '@rollup/rollup-linux-arm64-gnu': 4.18.0 + '@rollup/rollup-linux-arm64-musl': 4.18.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 + '@rollup/rollup-linux-riscv64-gnu': 4.18.0 + '@rollup/rollup-linux-s390x-gnu': 4.18.0 + '@rollup/rollup-linux-x64-gnu': 4.18.0 + '@rollup/rollup-linux-x64-musl': 4.18.0 + '@rollup/rollup-win32-arm64-msvc': 4.18.0 + '@rollup/rollup-win32-ia32-msvc': 4.18.0 + '@rollup/rollup-win32-x64-msvc': 4.18.0 fsevents: 2.3.3 dev: true @@ -3148,7 +3464,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: false /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -3163,8 +3478,8 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true @@ -3176,8 +3491,8 @@ packages: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true - /std-env@3.4.3: - resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} dev: true /string-argv@0.3.2: @@ -3224,10 +3539,10 @@ packages: engines: {node: '>=8'} dev: true - /strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} dependencies: - acorn: 8.10.0 + js-tokens: 9.0.0 dev: true /supports-color@7.2.0: @@ -3249,6 +3564,15 @@ packages: engines: {node: '>= 0.4'} dev: false + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -3257,8 +3581,8 @@ packages: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true - /tinypool@0.7.0: - resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + /tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} dev: true @@ -3267,6 +3591,11 @@ packages: engines: {node: '>=14.0.0'} dev: true + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3279,6 +3608,17 @@ packages: hasBin: true dev: true + /ts-essentials@9.4.2(typescript@5.2.2): + resolution: {integrity: sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==} + peerDependencies: + typescript: '>=4.1.0' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -3343,6 +3683,10 @@ packages: resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3360,17 +3704,16 @@ packages: punycode: 2.3.0 dev: true - /vite-node@0.34.6(@types/node@18.18.6): - resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} - engines: {node: '>=v14.18.0'} + /vite-node@1.6.0(@types/node@20.14.9): + resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@18.18.6) + vite: 5.3.1(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' - less @@ -3382,12 +3725,12 @@ packages: - terser dev: true - /vite@4.5.0(@types/node@18.18.6): - resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} - engines: {node: ^14.18.0 || >=16.0.0} + /vite@5.3.1(@types/node@20.14.9): + resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -3410,30 +3753,41 @@ packages: terser: optional: true dependencies: - '@types/node': 18.18.6 - esbuild: 0.18.20 - postcss: 8.4.31 - rollup: 3.29.4 + '@types/node': 20.14.9 + esbuild: 0.21.5 + postcss: 8.4.38 + rollup: 4.18.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@0.34.6: - resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} - engines: {node: '>=v14.18.0'} + /vitest-mock-extended@1.3.1(typescript@5.2.2)(vitest@1.6.0): + resolution: {integrity: sha512-OpghYjh4BDuQ/Mzs3lFMQ1QRk9D8/2O9T47MLUA5eLn7K4RWIy+MfIivYOWEyxjTENjsBnzgMihDjyNalN/K0Q==} + peerDependencies: + typescript: 3.x || 4.x || 5.x + vitest: '>=0.31.1' + dependencies: + ts-essentials: 9.4.2(typescript@5.2.2) + typescript: 5.2.2 + vitest: 1.6.0(@types/node@20.14.9) + dev: true + + /vitest@1.6.0(@types/node@20.14.9): + resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.0 + '@vitest/ui': 1.6.0 happy-dom: '*' jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/node': + optional: true '@vitest/browser': optional: true '@vitest/ui': @@ -3442,36 +3796,27 @@ packages: optional: true jsdom: optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true dependencies: - '@types/chai': 4.3.9 - '@types/chai-subset': 1.3.4 - '@types/node': 18.18.6 - '@vitest/expect': 0.34.6 - '@vitest/runner': 0.34.6 - '@vitest/snapshot': 0.34.6 - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 - acorn: 8.10.0 - acorn-walk: 8.2.0 - cac: 6.7.14 + '@types/node': 20.14.9 + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.3 chai: 4.3.10 debug: 4.3.4 - local-pkg: 0.4.3 + execa: 8.0.1 + local-pkg: 0.5.0 magic-string: 0.30.5 pathe: 1.1.1 picocolors: 1.0.0 - std-env: 3.4.3 - strip-literal: 1.3.0 + std-env: 3.7.0 + strip-literal: 2.1.0 tinybench: 2.5.1 - tinypool: 0.7.0 - vite: 4.5.0(@types/node@18.18.6) - vite-node: 0.34.6(@types/node@18.18.6) + tinypool: 0.8.4 + vite: 5.3.1(@types/node@20.14.9) + vite-node: 1.6.0(@types/node@20.14.9) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/core/CoreExtension.ts b/src/core/CoreExtension.ts deleted file mode 100644 index 00fb9ff2..00000000 --- a/src/core/CoreExtension.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Stage } from './Stage.js'; - -/** - * Base class for Core extensions. - * - * @remarks - * Core extensions are used to extend the Core Renderer with custom code such as - * custom fonts, custom shaders, custom textures, custom animation functions, - * and more. - */ -export abstract class CoreExtension { - abstract run(stage: Stage): Promise; -} diff --git a/src/core/CoreNode.test.ts b/src/core/CoreNode.test.ts new file mode 100644 index 00000000..fc945c34 --- /dev/null +++ b/src/core/CoreNode.test.ts @@ -0,0 +1,93 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { CoreNode, type CoreNodeProps, UpdateType } from './CoreNode.js'; +import { Stage } from './Stage.js'; +import { mock } from 'vitest-mock-extended'; +import { type TextureOptions } from './CoreTextureManager.js'; +import { type BaseShaderController } from '../main-api/ShaderController'; + +describe('set color()', () => { + const defaultProps: CoreNodeProps = { + alpha: 0, + autosize: false, + clipping: false, + color: 0, + colorBl: 0, + colorBottom: 0, + colorBr: 0, + colorLeft: 0, + colorRight: 0, + colorTl: 0, + colorTop: 0, + colorTr: 0, + height: 0, + mount: 0, + mountX: 0, + mountY: 0, + parent: null, + pivot: 0, + pivotX: 0, + pivotY: 0, + rotation: 0, + rtt: false, + scale: 0, + scaleX: 0, + scaleY: 0, + shader: mock(), + src: '', + texture: null, + textureOptions: {} as TextureOptions, + width: 0, + x: 0, + y: 0, + zIndex: 0, + zIndexLocked: 0, + }; + + it('should set all color subcomponents.', () => { + const node = new CoreNode(mock(), defaultProps); + node.colorBl = 0x99aabbff; + node.colorBr = 0xaabbccff; + node.colorTl = 0xbbcceeff; + node.colorTr = 0xcceeffff; + + node.color = 0xffffffff; + + expect(node.color).toBe(0xffffffff); + expect(node.colorBl).toBe(0xffffffff); + expect(node.colorBr).toBe(0xffffffff); + expect(node.colorTl).toBe(0xffffffff); + expect(node.colorTr).toBe(0xffffffff); + expect(node.colorLeft).toBe(0xffffffff); + expect(node.colorRight).toBe(0xffffffff); + expect(node.colorTop).toBe(0xffffffff); + expect(node.colorBottom).toBe(0xffffffff); + }); + + it('should set update type.', () => { + const node = new CoreNode(mock(), defaultProps); + node.updateType = 0; + + node.color = 0xffffffff; + + expect(node.updateType).toBe(UpdateType.PremultipliedColors); + }); +}); diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index a1650c79..79db1024 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -19,17 +19,11 @@ import { assertTruthy, + getNewId, mergeColorAlphaPremultiplied, - getImageAspectRatio, } from '../utils.js'; -import type { ShaderMap } from './CoreShaderManager.js'; -import type { - ExtractProps, - TextureMap, - TextureOptions, -} from './CoreTextureManager.js'; +import type { TextureOptions } from './CoreTextureManager.js'; import type { CoreRenderer } from './renderers/CoreRenderer.js'; -import type { CoreShader } from './renderers/CoreShader.js'; import type { Stage } from './Stage.js'; import type { Texture, @@ -37,7 +31,6 @@ import type { TextureFreedEventHandler, TextureLoadedEventHandler, } from './textures/Texture.js'; -import { RenderTexture } from './textures/RenderTexture.js'; import type { Dimensions, NodeTextureFailedPayload, @@ -55,6 +48,11 @@ import { } from './lib/utils.js'; import { Matrix3d } from './lib/Matrix3d.js'; import { RenderCoords } from './lib/RenderCoords.js'; +import type { AnimationSettings } from './animations/CoreAnimation.js'; +import type { IAnimationController } from '../common/IAnimationController.js'; +import { CoreAnimation } from './animations/CoreAnimation.js'; +import { CoreAnimationController } from './animations/CoreAnimationController.js'; +import type { BaseShaderController } from '../main-api/ShaderController.js'; export enum CoreNodeRenderState { Init = 0, @@ -69,50 +67,6 @@ CoreNodeRenderStateMap.set(CoreNodeRenderState.OutOfBounds, 'outOfBounds'); CoreNodeRenderStateMap.set(CoreNodeRenderState.InBounds, 'inBounds'); CoreNodeRenderStateMap.set(CoreNodeRenderState.InViewport, 'inViewport'); -export interface CoreNodeProps { - id: number; - // External facing properties whose defaults are determined by - // RendererMain's resolveNodeDefaults() method - x: number; - y: number; - width: number; - height: number; - alpha: number; - autosize: boolean; - clipping: boolean; - color: number; - colorTop: number; - colorBottom: number; - colorLeft: number; - colorRight: number; - colorTl: number; - colorTr: number; - colorBl: number; - colorBr: number; - parent: CoreNode | null; - zIndex: number; - texture: Texture | null; - textureOptions: TextureOptions | null; - shader: CoreShader | null; - shaderProps: Record | null; - zIndexLocked: number; - scaleX: number; - scaleY: number; - mount: number; - mountX: number; - mountY: number; - pivot: number; - pivotX: number; - pivotY: number; - rotation: number; - rtt: boolean; -} - -type ICoreNode = Omit< - CoreNodeProps, - 'texture' | 'textureOptions' | 'shader' | 'shaderProps' ->; - export enum UpdateType { /** * Child updates @@ -235,9 +189,462 @@ export enum UpdateType { All = 8191, } -export class CoreNode extends EventEmitter implements ICoreNode { +/** + * A custom data map which can be stored on an CoreNode + * + * @remarks + * This is a map of key-value pairs that can be stored on an INode. It is used + * to store custom data that can be used by the application. + * The data stored can only be of type string, number or boolean. + */ +export type CustomDataMap = { + [key: string]: string | number | boolean | undefined; +}; + +/** + * Writable properties of a Node. + */ +export interface CoreNodeProps { + /** + * The x coordinate of the Node's Mount Point. + * + * @remarks + * See {@link mountX} and {@link mountY} for more information about setting + * the Mount Point. + * + * @default `0` + */ + x: number; + /** + * The y coordinate of the Node's Mount Point. + * + * @remarks + * See {@link mountX} and {@link mountY} for more information about setting + * the Mount Point. + * + * @default `0` + */ + y: number; + /** + * The width of the Node. + * + * @default `0` + */ + width: number; + /** + * The height of the Node. + * + * @default `0` + */ + height: number; + /** + * The alpha opacity of the Node. + * + * @remarks + * The alpha value is a number between 0 and 1, where 0 is fully transparent + * and 1 is fully opaque. + * + * @default `1` + */ + alpha: number; + /** + * Autosize mode + * + * @remarks + * When enabled, when a texture is loaded into the Node, the Node will + * automatically resize to the dimensions of the texture. + * + * Text Nodes are always autosized based on their text content regardless + * of this mode setting. + * + * @default `false` + */ + autosize: boolean; + /** + * Clipping Mode + * + * @remarks + * Enable Clipping Mode when you want to prevent the drawing of a Node and + * its descendants from overflowing outside of the Node's x/y/width/height + * bounds. + * + * For WebGL, clipping is implemented using the high-performance WebGL + * operation scissor. As a consequence, clipping does not work for + * non-rectangular areas. So, if the element is rotated + * (by itself or by any of its ancestors), clipping will not work as intended. + * + * TODO: Add support for non-rectangular clipping either automatically or + * via Render-To-Texture. + * + * @default `false` + */ + clipping: boolean; + /** + * The color of the Node. + * + * @remarks + * The color value is a number in the format 0xRRGGBBAA, where RR is the red + * component, GG is the green component, BB is the blue component, and AA is + * the alpha component. + * + * Gradient colors may be set by setting the different color sub-properties: + * {@link colorTop}, {@link colorBottom}, {@link colorLeft}, {@link colorRight}, + * {@link colorTl}, {@link colorTr}, {@link colorBr}, {@link colorBl} accordingly. + * + * @default `0xffffffff` (opaque white) + */ + color: number; + /** + * The color of the top edge of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorTop: number; + /** + * The color of the bottom edge of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorBottom: number; + /** + * The color of the left edge of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorLeft: number; + /** + * The color of the right edge of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorRight: number; + /** + * The color of the top-left corner of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorTl: number; + /** + * The color of the top-right corner of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorTr: number; + /** + * The color of the bottom-right corner of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorBr: number; + /** + * The color of the bottom-left corner of the Node for gradient rendering. + * + * @remarks + * See {@link color} for more information about color values and gradient + * rendering. + */ + colorBl: number; + /** + * The Node's parent Node. + * + * @remarks + * The value `null` indicates that the Node has no parent. This may either be + * because the Node is the root Node of the scene graph, or because the Node + * has been removed from the scene graph. + * + * In order to make sure that a Node can be rendered on the screen, it must + * be added to the scene graph by setting it's parent property to a Node that + * is already in the scene graph such as the root Node. + * + * @default `null` + */ + parent: CoreNode | null; + /** + * The Node's z-index. + * + * @remarks + * TBD + */ + zIndex: number; + /** + * The Node's Texture. + * + * @remarks + * The `texture` defines a rasterized image that is contained within the + * {@link width} and {@link height} dimensions of the Node. If null, the + * Node will use an opaque white {@link ColorTexture} when being drawn, which + * essentially enables colors (including gradients) to be drawn. + * + * If set, by default, the texture will be drawn, as is, stretched to the + * dimensions of the Node. This behavior can be modified by setting the TBD + * and TBD properties. + * + * To create a Texture in order to set it on this property, call + * {@link RendererMain.createTexture}. + * + * If the {@link src} is set on a Node, the Node will use the + * {@link ImageTexture} by default and the Node will simply load the image at + * the specified URL. + * + * Note: If this is a Text Node, the Texture will be managed by the Node's + * {@link TextRenderer} and should not be set explicitly. + */ + texture: Texture | null; + + /** + * Options to associate with the Node's Texture + */ + textureOptions: TextureOptions; + + /** + * The Node's shader + * + * @remarks + * The `shader` defines a {@link Shader} used to draw the Node. By default, + * the Default Shader is used which simply draws the defined {@link texture} + * or {@link color}(s) within the Node without any special effects. + * + * To create a Shader in order to set it on this property, call + * {@link RendererMain.createShader}. + * + * Note: If this is a Text Node, the Shader will be managed by the Node's + * {@link TextRenderer} and should not be set explicitly. + */ + shader: BaseShaderController; + /** + * Image URL + * + * @remarks + * When set, the Node's {@link texture} is automatically set to an + * {@link ImageTexture} using the source image URL provided (with all other + * settings being defaults) + */ + src: string | null; + zIndexLocked: number; + /** + * Scale to render the Node at + * + * @remarks + * The scale value multiplies the provided {@link width} and {@link height} + * of the Node around the Node's Pivot Point (defined by the {@link pivot} + * props). + * + * Behind the scenes, setting this property sets both the {@link scaleX} and + * {@link scaleY} props to the same value. + * + * NOTE: When the scaleX and scaleY props are explicitly set to different values, + * this property returns `null`. Setting `null` on this property will have no + * effect. + * + * @default 1.0 + */ + scale: number | null; + /** + * Scale to render the Node at (X-Axis) + * + * @remarks + * The scaleX value multiplies the provided {@link width} of the Node around + * the Node's Pivot Point (defined by the {@link pivot} props). + * + * @default 1.0 + */ + scaleX: number; + /** + * Scale to render the Node at (Y-Axis) + * + * @remarks + * The scaleY value multiplies the provided {@link height} of the Node around + * the Node's Pivot Point (defined by the {@link pivot} props). + * + * @default 1.0 + */ + scaleY: number; + /** + * Combined position of the Node's Mount Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Mount Point at the top-left corner of the Node. + * - `0.5` defines it at the center of the Node. + * - `1.0` defines it at the bottom-right corner of the node. + * + * Use the {@link mountX} and {@link mountY} props seperately for more control + * of the Mount Point. + * + * When assigned, the same value is also passed to both the {@link mountX} and + * {@link mountY} props. + * + * @default 0 (top-left) + */ + mount: number; + /** + * X position of the Node's Mount Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Mount Point's X position as the left-most edge of the + * Node + * - `0.5` defines it as the horizontal center of the Node + * - `1.0` defines it as the right-most edge of the Node. + * + * The combination of {@link mountX} and {@link mountY} define the Mount Point + * + * @default 0 (left-most edge) + */ + mountX: number; + /** + * Y position of the Node's Mount Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Mount Point's Y position as the top-most edge of the + * Node + * - `0.5` defines it as the vertical center of the Node + * - `1.0` defines it as the bottom-most edge of the Node. + * + * The combination of {@link mountX} and {@link mountY} define the Mount Point + * + * @default 0 (top-most edge) + */ + mountY: number; + /** + * Combined position of the Node's Pivot Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Pivot Point at the top-left corner of the Node. + * - `0.5` defines it at the center of the Node. + * - `1.0` defines it at the bottom-right corner of the node. + * + * Use the {@link pivotX} and {@link pivotY} props seperately for more control + * of the Pivot Point. + * + * When assigned, the same value is also passed to both the {@link pivotX} and + * {@link pivotY} props. + * + * @default 0.5 (center) + */ + pivot: number; + /** + * X position of the Node's Pivot Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Pivot Point's X position as the left-most edge of the + * Node + * - `0.5` defines it as the horizontal center of the Node + * - `1.0` defines it as the right-most edge of the Node. + * + * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point + * + * @default 0.5 (centered on x-axis) + */ + pivotX: number; + /** + * Y position of the Node's Pivot Point + * + * @remarks + * The value can be any number between `0.0` and `1.0`: + * - `0.0` defines the Pivot Point's Y position as the top-most edge of the + * Node + * - `0.5` defines it as the vertical center of the Node + * - `1.0` defines it as the bottom-most edge of the Node. + * + * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point + * + * @default 0.5 (centered on y-axis) + */ + pivotY: number; + /** + * Rotation of the Node (in Radians) + * + * @remarks + * Sets the amount to rotate the Node by around it's Pivot Point (defined by + * the {@link pivot} props). Positive values rotate the Node clockwise, while + * negative values rotate it counter-clockwise. + * + * Example values: + * - `-Math.PI / 2`: 90 degree rotation counter-clockwise + * - `0`: No rotation + * - `Math.PI / 2`: 90 degree rotation clockwise + * - `Math.PI`: 180 degree rotation clockwise + * - `3 * Math.PI / 2`: 270 degree rotation clockwise + * - `2 * Math.PI`: 360 rotation clockwise + */ + rotation: number; + + /** + * Whether the Node is rendered to a texture + * + * @remarks + * TBD + * + * @default false + */ + rtt: boolean; + + /** + * Node data element for custom data storage (optional) + * + * @remarks + * This property is used to store custom data on the Node as a key/value data store. + * Data values are limited to string, numbers, booleans. Strings will be truncated + * to a 2048 character limit for performance reasons. + * + * This is not a data storage mechanism for large amounts of data please use a + * dedicated data storage mechanism for that. + * + * The custom data will be reflected in the inspector as part of `data-*` attributes + * + * @default `undefined` + */ + data?: CustomDataMap; +} + +/** + * Grab all the number properties of type T + */ +type NumberProps = { + [Key in keyof T as NonNullable extends number ? Key : never]: number; +}; + +/** + * Properties of a Node used by the animate() function + */ +export interface CoreNodeAnimateProps extends NumberProps { + /** + * Shader properties to animate + */ + shaderProps: Record; + // TODO: textureProps: Record; +} + +/** + * A visual Node in the Renderer scene graph. + * + * @remarks + * CoreNode is an internally used class that represents a Renderer Node in the + * scene graph. See INode.ts for the public APIs exposed to Renderer users + * that include generic types for Shaders. + */ +export class CoreNode extends EventEmitter { readonly children: CoreNode[] = []; - protected props: Required; + protected _id: number = getNewId(); + readonly props: CoreNodeProps; public updateType = UpdateType.All; @@ -267,47 +674,46 @@ export class CoreNode extends EventEmitter implements ICoreNode { public hasRTTupdates = false; public parentHasRenderTexture = false; - constructor(protected stage: Stage, props: CoreNodeProps) { + constructor(readonly stage: Stage, props: CoreNodeProps) { super(); + this.props = { ...props, parent: null, + texture: null, + src: null, + rtt: false, }; - // Allow for parent to be processed appropriately - this.parent = props.parent; - // Allow for Render Texture to be processed appropriately + // Assign props to instance + this.parent = props.parent; + this.texture = props.texture; + this.src = props.src; this.rtt = props.rtt; this.updateScaleRotateTransform(); } //#region Textures - loadTexture( - textureType: Type, - props: ExtractProps, - options: TextureOptions | null = null, - ): void { - // First unload any existing texture - if (this.props.texture) { - this.unloadTexture(); - } - const { txManager } = this.stage; - const texture = txManager.loadTexture(textureType, props, options); - - this.props.texture = texture; - this.props.textureOptions = options; - this.setUpdateType(UpdateType.IsRenderable); + loadTexture(): void { + const { texture } = this.props; + assertTruthy(texture); // If texture is already loaded / failed, trigger loaded event manually // so that users get a consistent event experience. // We do this in a microtask to allow listeners to be attached in the same // synchronous task after calling loadTexture() queueMicrotask(() => { + // Preload texture if required + if (this.textureOptions.preload) { + texture.ctxTexture.load(); + } if (texture.state === 'loaded') { - this.onTextureLoaded(texture, texture.dimensions!); + assertTruthy(texture.dimensions); + this.onTextureLoaded(texture, texture.dimensions); } else if (texture.state === 'failed') { - this.onTextureFailed(texture, texture.error!); + assertTruthy(texture.error); + this.onTextureFailed(texture, texture.error); } else if (texture.state === 'freed') { this.onTextureFreed(texture); } @@ -318,16 +724,12 @@ export class CoreNode extends EventEmitter implements ICoreNode { } unloadTexture(): void { - if (this.props.texture) { - const { texture } = this.props; - texture.off('loaded', this.onTextureLoaded); - texture.off('failed', this.onTextureFailed); - texture.off('freed', this.onTextureFreed); - texture.setRenderableOwner(this, false); + if (this.texture) { + this.texture.off('loaded', this.onTextureLoaded); + this.texture.off('failed', this.onTextureFailed); + this.texture.off('freed', this.onTextureFreed); + this.texture.setRenderableOwner(this, false); } - this.props.texture = null; - this.props.textureOptions = null; - this.setUpdateType(UpdateType.IsRenderable); } autosizeNode(dimensions: Dimensions) { @@ -337,7 +739,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { } } - private onTextureLoaded: TextureLoadedEventHandler = (target, dimensions) => { + private onTextureLoaded: TextureLoadedEventHandler = (_, dimensions) => { this.autosizeNode(dimensions); // Texture was loaded. In case the RAF loop has already stopped, we request @@ -361,32 +763,20 @@ export class CoreNode extends EventEmitter implements ICoreNode { } }; - private onTextureFailed: TextureFailedEventHandler = (target, error) => { + private onTextureFailed: TextureFailedEventHandler = (_, error) => { this.emit('failed', { type: 'texture', error, } satisfies NodeTextureFailedPayload); }; - private onTextureFreed: TextureFreedEventHandler = (target: Texture) => { + private onTextureFreed: TextureFreedEventHandler = () => { this.emit('freed', { type: 'texture', } satisfies NodeTextureFreedPayload); }; //#endregion Textures - loadShader( - shaderType: Type, - props: ExtractProps, - ): void { - const shManager = this.stage.renderer.getShaderManager(); - assertTruthy(shManager); - const { shader, props: p } = shManager.loadShader(shaderType, props); - this.props.shader = shader; - this.props.shaderProps = p; - this.setUpdateType(UpdateType.IsRenderable); - } - /** * Change types types is used to determine the scope of the changes being applied * @@ -416,29 +806,45 @@ export class CoreNode extends EventEmitter implements ICoreNode { } updateScaleRotateTransform() { + const { rotation, scaleX, scaleY } = this.props; + + // optimize simple translation cases + if (rotation === 0 && scaleX === 1 && scaleY === 1) { + this.scaleRotateTransform = undefined; + return; + } + this.scaleRotateTransform = Matrix3d.rotate( - this.props.rotation, + rotation, this.scaleRotateTransform, - ).scale(this.props.scaleX, this.props.scaleY); + ).scale(scaleX, scaleY); } updateLocalTransform() { - assertTruthy(this.scaleRotateTransform); - const pivotTranslateX = this.props.pivotX * this.props.width; - const pivotTranslateY = this.props.pivotY * this.props.height; - const mountTranslateX = this.props.mountX * this.props.width; - const mountTranslateY = this.props.mountY * this.props.height; - - this.localTransform = Matrix3d.translate( - pivotTranslateX - mountTranslateX + this.props.x, - pivotTranslateY - mountTranslateY + this.props.y, - this.localTransform, - ) - .multiply(this.scaleRotateTransform) - .translate(-pivotTranslateX, -pivotTranslateY); + const { x, y, width, height } = this.props; + const mountTranslateX = this.props.mountX * width; + const mountTranslateY = this.props.mountY * height; + + if (this.scaleRotateTransform) { + const pivotTranslateX = this.props.pivotX * width; + const pivotTranslateY = this.props.pivotY * height; + + this.localTransform = Matrix3d.translate( + x - mountTranslateX + pivotTranslateX, + y - mountTranslateY + pivotTranslateY, + this.localTransform, + ) + .multiply(this.scaleRotateTransform) + .translate(-pivotTranslateX, -pivotTranslateY); + } else { + this.localTransform = Matrix3d.translate( + x - mountTranslateX, + y - mountTranslateY, + this.localTransform, + ); + } // Handle 'contain' resize mode - const { width, height } = this.props; const texture = this.props.texture; if ( texture && @@ -650,7 +1056,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { return false; } - if (this.props.shader) { + if (this.props.shader !== this.stage.defShaderCtr) { return true; } @@ -710,19 +1116,19 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.strictBound, ); + if (boundInsideBound(this.renderBound, this.strictBound)) { + return CoreNodeRenderState.InViewport; + } + const renderM = this.stage.boundsMargin; this.preloadBound = createBound( - parentClippingRect.x - renderM[3], - parentClippingRect.y - renderM[0], - parentClippingRect.x + rectW + renderM[1], - parentClippingRect.y + rectH + renderM[2], + this.strictBound.x1 - renderM[3], + this.strictBound.y1 - renderM[0], + this.strictBound.x2 + renderM[1], + this.strictBound.y2 + renderM[2], this.preloadBound, ); - if (boundInsideBound(this.renderBound, this.strictBound)) { - return CoreNodeRenderState.InViewport; - } - if (boundInsideBound(this.renderBound, this.preloadBound)) { return CoreNodeRenderState.InBounds; } @@ -731,48 +1137,19 @@ export class CoreNode extends EventEmitter implements ICoreNode { updateRenderState(parentClippingRect: RectWithValid) { const renderState = this.checkRenderBounds(parentClippingRect); - if (renderState !== this.renderState) { - let previous = this.renderState; - this.renderState = renderState; - if (previous === CoreNodeRenderState.InViewport) { - this.emit('outOfViewport', { - previous, - current: renderState, - }); - } - if ( - previous < CoreNodeRenderState.InBounds && - renderState === CoreNodeRenderState.InViewport - ) { - this.emit(CoreNodeRenderStateMap.get(CoreNodeRenderState.InBounds)!, { - previous, - current: renderState, - }); - previous = CoreNodeRenderState.InBounds; - } else if ( - previous > CoreNodeRenderState.InBounds && - renderState === CoreNodeRenderState.OutOfBounds - ) { - this.emit(CoreNodeRenderStateMap.get(CoreNodeRenderState.InBounds)!, { - previous, - current: renderState, - }); - previous = CoreNodeRenderState.InBounds; - } - const event = CoreNodeRenderStateMap.get(renderState); - assertTruthy(event); - this.emit(event, { - previous, - current: renderState, - }); - } - } - setRenderState(state: CoreNodeRenderState) { - if (state !== this.renderState) { - this.renderState = state; - this.emit(CoreNodeRenderState[state]); + if (renderState === this.renderState) { + return; } + + const previous = this.renderState; + this.renderState = renderState; + const event = CoreNodeRenderStateMap.get(renderState); + assertTruthy(event); + this.emit(event, { + previous, + current: renderState, + }); } /** @@ -794,7 +1171,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { } onChangeIsRenderable(isRenderable: boolean) { - this.props.texture?.setRenderableOwner(this, isRenderable); + this.texture?.setRenderableOwner(this, isRenderable); } calculateRenderCoords() { @@ -927,19 +1304,25 @@ export class CoreNode extends EventEmitter implements ICoreNode { delete this.localTransform; this.props.texture = null; - this.props.shader = null; + this.props.shader = this.stage.defShaderCtr; + + const children = [...this.children]; + for (let i = 0; i < children.length; i++) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + children[i]!.destroy(); + } + // This very action will also remove the node from the parent's children array + this.parent = null; if (this.rtt) { this.stage.renderer.removeRTTNode(this); } this.removeAllListeners(); - this.parent = null; } renderQuads(renderer: CoreRenderer): void { - const { width, height, texture, textureOptions, shader, shaderProps, rtt } = - this.props; + const { texture, width, height, textureOptions, rtt, shader } = this.props; // Prevent quad rendering if parent has a render texture // and renderer is not currently rendering to a texture @@ -960,9 +1343,16 @@ export class CoreNode extends EventEmitter implements ICoreNode { premultipliedColorBr, } = this; - const { zIndex, worldAlpha, globalTransform: gt, clippingRect } = this; + const { + zIndex, + worldAlpha, + globalTransform: gt, + clippingRect, + renderCoords, + } = this; assertTruthy(gt); + assertTruthy(renderCoords); // add to list of renderables to be sorted before rendering renderer.addQuad({ @@ -975,8 +1365,8 @@ export class CoreNode extends EventEmitter implements ICoreNode { texture, textureOptions, zIndex, - shader, - shaderProps, + shader: shader.shader, + shaderProps: shader.getResolvedProps(), alpha: worldAlpha, clippingRect, tx: gt.tx, @@ -985,6 +1375,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { tb: gt.tb, tc: gt.tc, td: gt.td, + renderCoords, rtt, parentHasRenderTexture: this.parentHasRenderTexture, framebufferDimensions: this.framebufferDimensions, @@ -993,7 +1384,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { //#region Properties get id(): number { - return this.props.id; + return this._id; } get x(): number { @@ -1039,6 +1430,11 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.setUpdateType(UpdateType.Local); if (this.props.rtt) { + this.texture = this.stage.txManager.loadTexture('RenderTexture', { + width: this.width, + height: this.height, + }); + this.textureOptions.preload = true; this.setUpdateType(UpdateType.RenderTexture); } } @@ -1054,6 +1450,11 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.setUpdateType(UpdateType.Local); if (this.props.rtt) { + this.texture = this.stage.txManager.loadTexture('RenderTexture', { + width: this.width, + height: this.height, + }); + this.textureOptions.preload = true; this.setUpdateType(UpdateType.RenderTexture); } } @@ -1206,17 +1607,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set color(value: number) { - if ( - this.props.colorTl !== value || - this.props.colorTr !== value || - this.props.colorBl !== value || - this.props.colorBr !== value - ) { - this.colorTl = value; - this.colorTr = value; - this.colorBl = value; - this.colorBr = value; - } + this.colorTop = value; + this.colorBottom = value; + this.colorLeft = value; + this.colorRight = value; this.props.color = value; this.setUpdateType(UpdateType.PremultipliedColors); @@ -1378,9 +1772,11 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set rtt(value: boolean) { - if (!value) { - if (this.props.rtt) { - this.props.rtt = false; + if (this.props.rtt === true) { + this.props.rtt = value; + + // unload texture if we used to have a render texture + if (value === false && this.texture !== null) { this.unloadTexture(); this.setUpdateType(UpdateType.All); @@ -1389,10 +1785,22 @@ export class CoreNode extends EventEmitter implements ICoreNode { }); this.stage.renderer?.removeRTTNode(this); + return; } + } + + // if the new value is false and we didnt have rtt previously, we don't need to do anything + if (value === false) { return; } + // load texture + this.texture = this.stage.txManager.loadTexture('RenderTexture', { + width: this.width, + height: this.height, + }); + this.textureOptions.preload = true; + this.props.rtt = true; this.hasRTTupdates = true; this.setUpdateType(UpdateType.All); @@ -1405,6 +1813,41 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.stage.renderer?.renderToTexture(this); } + get shader(): BaseShaderController { + return this.props.shader; + } + + set shader(value: BaseShaderController) { + if (this.props.shader === value) { + return; + } + + this.props.shader = value; + + this.setUpdateType(UpdateType.IsRenderable); + } + + get src(): string | null { + return this.props.src; + } + + set src(imageUrl: string | null) { + if (this.props.src === imageUrl) { + return; + } + + this.props.src = imageUrl; + + if (!imageUrl) { + this.texture = null; + return; + } + + this.texture = this.stage.txManager.loadTexture('ImageTexture', { + src: imageUrl, + }); + } + /** * Returns the framebuffer dimensions of the node. * If the node has a render texture, the dimensions are the same as the node's dimensions. @@ -1436,9 +1879,53 @@ export class CoreNode extends EventEmitter implements ICoreNode { return this.props.texture; } + set texture(value: Texture | null) { + if (this.props.texture === value) { + return; + } + const oldTexture = this.props.texture; + if (oldTexture) { + oldTexture.setRenderableOwner(this, false); + this.unloadTexture(); + } + this.props.texture = value; + if (value) { + value.setRenderableOwner(this, this.isRenderable); + this.loadTexture(); + } + this.setUpdateType(UpdateType.IsRenderable); + } + + set textureOptions(value: TextureOptions) { + this.props.textureOptions = value; + } + + get textureOptions(): TextureOptions { + return this.props.textureOptions; + } + setRTTUpdates(type: number) { this.hasRTTupdates = true; this.parent?.setRTTUpdates(type); } + + animate( + props: Partial, + settings: Partial, + ): IAnimationController { + const animation = new CoreAnimation(this, props, settings); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const controller = new CoreAnimationController( + this.stage.animationManager, + animation, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return controller; + } + + flush() { + // no-op + } + //#endregion Properties } diff --git a/src/core/CoreShaderManager.ts b/src/core/CoreShaderManager.ts index 8a71e504..f9a8ef0c 100644 --- a/src/core/CoreShaderManager.ts +++ b/src/core/CoreShaderManager.ts @@ -66,6 +66,11 @@ import { } from './renderers/webgl/shaders/effects/HolePunchEffect.js'; import { WebGlCoreShader } from './renderers/webgl/WebGlCoreShader.js'; import { UnsupportedShader } from './renderers/canvas/shaders/UnsupportedShader.js'; +import { ShaderController } from '../main-api/ShaderController.js'; +import { + DynamicShaderController, + type DynamicEffects, +} from '../main-api/DynamicShaderController.js'; export type { FadeOutEffectProps }; export type { LinearGradientEffectProps }; @@ -84,11 +89,6 @@ export interface ShaderMap { UnsupportedShader: typeof UnsupportedShader; } -export type ShaderNode = { - shader: InstanceType; - props: Record; -}; - export interface EffectMap { radius: typeof RadiusEffect; border: typeof BorderEffect; @@ -166,10 +166,17 @@ export class CoreShaderManager { return this.shConstructors; } + /** + * Loads a shader (if not already loaded) and returns a controller for it. + * + * @param shType + * @param props + * @returns + */ loadShader( shType: Type, props?: ExtractProps, - ): ShaderNode { + ): ShaderController { if (!this.renderer) { throw new Error(`Renderer is not been defined`); } @@ -182,14 +189,17 @@ export class CoreShaderManager { this.renderer.mode === 'canvas' && ShaderClass.prototype instanceof WebGlCoreShader ) { - return { - shader: new UnsupportedShader(shType) as InstanceType, - props: props as Record, - }; + return this._createShaderCtr( + shType, + new UnsupportedShader(shType) as InstanceType, + props as ExtractProps, + ); } if (shType === 'DynamicShader') { - return this.loadDynamicShader(props!); + return this.loadDynamicShader( + props!, + ) as unknown as ShaderController; } const resolvedProps = ShaderClass.resolveDefaults( @@ -198,10 +208,11 @@ export class CoreShaderManager { const cacheKey = ShaderClass.makeCacheKey(resolvedProps) || ShaderClass.name; if (cacheKey && this.shCache.has(cacheKey)) { - return { - shader: this.shCache.get(cacheKey) as InstanceType, - props: resolvedProps, - }; + return this._createShaderCtr( + shType, + this.shCache.get(cacheKey) as InstanceType, + resolvedProps as ExtractProps, + ); } // @ts-expect-error ShaderClass WILL accept a Renderer @@ -211,15 +222,16 @@ export class CoreShaderManager { if (cacheKey) { this.shCache.set(cacheKey, shader); } - return { + return this._createShaderCtr( + shType, shader, - props: resolvedProps, - }; + resolvedProps as ExtractProps, + ); } - loadDynamicShader( - props: DynamicShaderProps, - ): ShaderNode { + loadDynamicShader< + T extends DynamicEffects<[...{ name?: string; type: keyof EffectMap }[]]>, + >(props: DynamicShaderProps): DynamicShaderController { if (!this.renderer) { throw new Error(`Renderer is not been defined`); } @@ -232,10 +244,10 @@ export class CoreShaderManager { this.effectConstructors, ); if (cacheKey && this.shCache.has(cacheKey)) { - return { - shader: this.shCache.get(cacheKey) as InstanceType, - props: resolvedProps, - }; + return this._createDynShaderCtr( + this.shCache.get(cacheKey) as InstanceType, + resolvedProps, + ); } const shader = new DynamicShader( this.renderer as WebGlCoreRenderer, @@ -245,10 +257,25 @@ export class CoreShaderManager { if (cacheKey) { this.shCache.set(cacheKey, shader); } - return { - shader: shader as InstanceType, - props: resolvedProps, - }; + + return this._createDynShaderCtr(shader, resolvedProps); + } + + private _createShaderCtr( + type: Type, + shader: InstanceType, + props: ExtractProps, + ): ShaderController { + return new ShaderController(type, shader, props, this.renderer.stage); + } + + private _createDynShaderCtr< + T extends DynamicEffects<[...{ name?: string; type: keyof EffectMap }[]]>, + >( + shader: InstanceType, + props: ExtractProps, + ): DynamicShaderController { + return new DynamicShaderController(shader, props, this); } useShader(shader: CoreShader): void { diff --git a/src/core/CoreTextNode.ts b/src/core/CoreTextNode.ts index a498390b..c2507833 100644 --- a/src/core/CoreTextNode.ts +++ b/src/core/CoreTextNode.ts @@ -32,21 +32,44 @@ import type { NodeTextFailedPayload, NodeTextLoadedPayload, } from '../common/CommonTypes.js'; -import type { Rect, RectWithValid } from './lib/utils.js'; +import type { RectWithValid } from './lib/utils.js'; import { assertTruthy } from '../utils.js'; import { Matrix3d } from './lib/Matrix3d.js'; export interface CoreTextNodeProps extends CoreNodeProps, TrProps { - text: string; + /** + * Force Text Node to use a specific Text Renderer + * + * @remarks + * By default, Text Nodes resolve the Text Renderer to use based on the font + * that is matched using the font family and other font selection properties. + * + * If two fonts supported by two separate Text Renderers are matched setting + * this override forces the Text Node to resolve to the Text Renderer defined + * here. + * + * @default null + */ textRendererOverride: keyof TextRendererMap | null; } -type ICoreTextNode = Omit< - CoreTextNodeProps, - 'texture' | 'textureOptions' | 'shader' | 'shaderProps' ->; - -export class CoreTextNode extends CoreNode implements ICoreTextNode { +/** + * An CoreNode in the Renderer scene graph that renders text. + * + * @remarks + * A Text Node is the second graphical building block of the Renderer scene + * graph. It renders text using a specific text renderer that is automatically + * chosen based on the font requested and what type of fonts are installed + * into an app. + * + * The text renderer can be overridden by setting the `textRendererOverride` + * + * The `texture` and `shader` properties are managed by loaded text renderer and + * should not be set directly. + * + * For non-text rendering, see {@link CoreNode}. + */ +export class CoreTextNode extends CoreNode implements CoreTextNodeProps { textRenderer: TextRenderer; trState: TextRendererState; private _textRendererOverride: CoreTextNodeProps['textRendererOverride'] = @@ -356,6 +379,15 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode { override renderQuads(renderer: CoreRenderer) { assertTruthy(this.globalTransform); + // If the text renderer does not support rendering quads, fallback to the + // default renderQuads method + if (!this.textRenderer.renderQuads) { + super.renderQuads(renderer); + return; + } + + // If the text renderer does support rendering quads, use it... + // Prevent quad rendering if parent has a render texture // and this node is not the render texture if (this.parentHasRenderTexture) { @@ -410,7 +442,7 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode { this._textRendererOverride, ); - const textRendererState = resolvedTextRenderer.createState(props); + const textRendererState = resolvedTextRenderer.createState(props, this); textRendererState.emitter.on('loaded', this.onTextLoaded); textRendererState.emitter.on('failed', this.onTextFailed); diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index 404b3e64..8934e1ed 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -17,9 +17,7 @@ * limitations under the License. */ -import { assertTruthy } from '../utils.js'; import { ImageWorkerManager } from './lib/ImageWorker.js'; -import type { CoreContextTexture } from './renderers/CoreContextTexture.js'; import type { CoreRenderer } from './renderers/CoreRenderer.js'; import { ColorTexture } from './textures/ColorTexture.js'; import { ImageTexture } from './textures/ImageTexture.js'; @@ -29,7 +27,7 @@ import { RenderTexture } from './textures/RenderTexture.js'; import type { Texture } from './textures/Texture.js'; /** - * Augmentable map of texture types + * Augmentable map of texture class types * * @remarks * This interface can be augmented by other modules/apps to add additional @@ -54,7 +52,6 @@ export type ExtractProps = Type extends { z$__type__Props: infer Props } */ export interface TextureManagerDebugInfo { keyCacheSize: number; - idCacheSize: number; } export type ResizeModeOptions = @@ -117,35 +114,6 @@ export interface TextureOptions { */ preload?: boolean; - /** - * ID to use for this texture. - * - * @remarks - * This is for internal use only as an optimization. - * - * @privateRemarks - * This is used to avoid having to look up the texture in the texture cache - * by its cache key. Theoretically this should be faster. - * - * @defaultValue Automatically generated - */ - id?: number; - - /** - * Cache key to use for this texture - * - * @remarks - * If this is set, the texture will be cached using this key. If a texture - * with the same key is already cached, it will be returned instead of - * creating a new texture. - * - * If this is not set (undefined), it will be automatically generated via - * the specified `Texture`'s `makeCacheKey()` method. - * - * @defaultValue Automatically generated via `Texture.makeCacheKey()` - */ - cacheKey?: string | false; - /** * Flip the texture horizontally when rendering * @@ -172,20 +140,20 @@ export interface TextureOptions { export class CoreTextureManager { /** - * Amount of used memory defined in pixels + * Map of textures by cache key */ - usedMemory = 0; + keyCache: Map = new Map(); - txConstructors: Partial = {}; + /** + * Map of cache keys by texture + */ + inverseKeyCache: WeakMap = new WeakMap(); - textureKeyCache: Map = new Map(); - textureIdCache: Map = new Map(); + /** + * Map of texture constructors by their type name + */ + txConstructors: Partial = {}; - ctxTextureCache: WeakMap = new WeakMap(); - textureRefCountMap: WeakMap< - Texture, - { cacheKey: string | false; count: number } - > = new WeakMap(); imageWorkerManager: ImageWorkerManager | null = null; hasCreateImageBitmap = !!self.createImageBitmap; hasWorker = !!self.Worker; @@ -198,6 +166,17 @@ export class CoreTextureManager { */ renderer!: CoreRenderer; + /** + * The current frame time in milliseconds + * + * @remarks + * This is used to populate the `lastRenderableChangeTime` property of + * {@link Texture} instances when their renderable state changes. + * + * Set by stage via `updateFrameTime` method. + */ + frameTime = 0; + constructor(numImageWorkers: number) { // Register default known texture types if (this.hasCreateImageBitmap && this.hasWorker && numImageWorkers > 0) { @@ -227,155 +206,49 @@ export class CoreTextureManager { loadTexture( textureType: Type, props: ExtractProps, - options: TextureOptions | null = null, ): InstanceType { + let texture: Texture | undefined; const TextureClass = this.txConstructors[textureType]; if (!TextureClass) { throw new Error(`Texture type "${textureType}" is not registered`); } - let texture: Texture | undefined; - // If an ID is specified, try to get the texture from the ID cache first - if (options?.id !== undefined && this.textureIdCache.has(options.id)) { - // console.log('Getting texture by texture desc ID', options.id); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - texture = this.textureIdCache.get(options.id)!; - } - // If the texture is not found in the ID cache, try to get it from the key cache + if (!texture) { - const descId = options?.id; - const cacheKey = - options?.cacheKey ?? TextureClass.makeCacheKey(props as any); - if (cacheKey && this.textureKeyCache.has(cacheKey)) { + const cacheKey = TextureClass.makeCacheKey(props as any); + if (cacheKey && this.keyCache.has(cacheKey)) { // console.log('Getting texture by cache key', cacheKey); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - texture = this.textureKeyCache.get(cacheKey)!; + texture = this.keyCache.get(cacheKey)!; } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any texture = new TextureClass(this, props as any); - } - if (descId) { - this.addTextureIdToCache(descId, cacheKey, texture); - } - } - if (options?.preload) { - const ctxTx = this.getCtxTexture(texture); - ctxTx.load(); - } - return texture as InstanceType; - } - - /** - * Add a `Texture` to the texture cache by its texture desc ID and cache key - * - * @remarks - * This is used internally by the `CoreTextureManager` to cache textures - * when they are created. - * - * It handles updating the texture ID cache, texture key cache, and texture - * reference count map. - * - * @param textureDescId - * @param cacheKey - * @param texture - */ - private addTextureIdToCache( - textureDescId: number, - cacheKey: string | false, - texture: Texture, - ): void { - const { textureIdCache, textureRefCountMap } = this; - textureIdCache.set(textureDescId, texture); - if (textureRefCountMap.has(texture)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - textureRefCountMap.get(texture)!.count++; - } else { - textureRefCountMap.set(texture, { cacheKey, count: 1 }); - if (cacheKey) { - this.textureKeyCache.set(cacheKey, texture); - } - } - } - - /** - * Remove a `Texture` from the texture cache by its texture desc ID - * - * @remarks - * This is called externally by when we know (at least reasonably well) that - * the `TextureRef` in the Main API space has been is no longer used. This - * allows us to remove the `Texture` from the Usage Cache so that it can be - * garbage collected as well. - * - * @param textureDescId - */ - removeTextureIdFromCache(textureDescId: number): void { - const { textureIdCache, textureRefCountMap } = this; - const texture = textureIdCache.get(textureDescId); - if (!texture) { - // Sometimes a texture is removed from the cache before it ever gets - // added to the cache. This is fine and not an error. - return; - } - textureIdCache.delete(textureDescId); - if (textureRefCountMap.has(texture)) { - const refCountObj = textureRefCountMap.get(texture); - assertTruthy(refCountObj); - refCountObj.count--; - if (refCountObj.count === 0) { - textureRefCountMap.delete(texture); - // If the texture is not referenced anywhere else, remove it from the key cache - // as well. - // This should allow the `Texture` instance to be garbage collected. - if (refCountObj.cacheKey) { - this.textureKeyCache.delete(refCountObj.cacheKey); + if (cacheKey) { + this.initTextureToCache(texture, cacheKey); } } } - // Free the ctx texture if it exists. - this.ctxTextureCache.get(texture)?.free(); + return texture as InstanceType; } - /** - * Get an object containing debug information about the texture manager. - * - * @returns - */ - getDebugInfo(): TextureManagerDebugInfo { - // const textureSet = new Set(); - // for (const texture of this.textureIdCache.values()) { - // textureSet.add(texture); - // } - // for (const texture of this.textureKeyCache.values()) { - // textureSet.add(texture); - // } - // TODO: Output number of bytes used by textures - return { - keyCacheSize: this.textureKeyCache.size, - idCacheSize: this.textureIdCache.size, - }; + private initTextureToCache(texture: Texture, cacheKey: string) { + const { keyCache, inverseKeyCache } = this; + keyCache.set(cacheKey, texture); + inverseKeyCache.set(texture, cacheKey); } /** - * Get a CoreContextTexture for the given Texture source. + * Remove a texture from the cache * * @remarks - * If the texture source already has an allocated CoreContextTexture, it will be - * returned from the cache. Otherwise, a new CoreContextTexture will be created - * and cached. - * - * ContextTextures are stored in a WeakMap, so they will be garbage collected - * when the Texture source is no longer referenced. + * Called by Texture Cleanup when a texture is freed. * - * @param textureSource - * @returns + * @param texture */ - getCtxTexture(textureSource: Texture): CoreContextTexture { - if (this.ctxTextureCache.has(textureSource)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.ctxTextureCache.get(textureSource)!; + removeTextureFromCache(texture: Texture) { + const { inverseKeyCache, keyCache } = this; + const cacheKey = inverseKeyCache.get(texture); + if (cacheKey) { + keyCache.delete(cacheKey); } - const texture = this.renderer.createCtxTexture(textureSource); - - this.ctxTextureCache.set(textureSource, texture); - return texture; } } diff --git a/src/core/Stage.ts b/src/core/Stage.ts index a9961fd1..b306edae 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -20,10 +20,10 @@ import { startLoop, getTimeStamp } from './platform.js'; import { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js'; import { assertTruthy, setPremultiplyMode } from '../utils.js'; import { AnimationManager } from './animations/AnimationManager.js'; -import { CoreNode } from './CoreNode.js'; +import { CoreNode, type CoreNodeProps } from './CoreNode.js'; import { CoreTextureManager } from './CoreTextureManager.js'; import { TrFontManager } from './text-rendering/TrFontManager.js'; -import { CoreShaderManager } from './CoreShaderManager.js'; +import { CoreShaderManager, type ShaderMap } from './CoreShaderManager.js'; import type { TextRenderer, TextRendererMap, @@ -37,18 +37,23 @@ import type { FpsUpdatePayload, FrameTickPayload, } from '../common/CommonTypes.js'; -import { TextureMemoryManager } from './TextureMemoryManager.js'; +import { + TextureMemoryManager, + type TextureMemoryManagerSettings, +} from './TextureMemoryManager.js'; import type { CoreRenderer, CoreRendererOptions, } from './renderers/CoreRenderer.js'; import { CanvasCoreRenderer } from './renderers/canvas/CanvasCoreRenderer.js'; +import type { BaseShaderController } from '../main-api/ShaderController.js'; +import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js'; +import { santizeCustomDataMap } from '../main-api/utils.js'; export interface StageOptions { - rootId: number; appWidth: number; appHeight: number; - txMemByteThreshold: number; + textureMemory: TextureMemoryManagerSettings; boundsMargin: number | [number, number, number, number]; deviceLogicalPixelRatio: number; devicePhysicalPixelRatio: number; @@ -58,10 +63,8 @@ export interface StageOptions { enableContextSpy: boolean; numImageWorkers: number; renderMode: 'webgl' | 'canvas'; - - debug?: { - monitorTextureCache?: boolean; - }; + eventBus: EventEmitter; + quadBufferSize: number; } export type StageFpsUpdateHandler = ( @@ -77,7 +80,7 @@ export type StageFrameTickHandler = ( const bufferMemory = 2e6; const autoStart = true; -export class Stage extends EventEmitter { +export class Stage { /// Module Instances public readonly animationManager: AnimationManager; public readonly txManager: CoreTextureManager; @@ -88,6 +91,17 @@ export class Stage extends EventEmitter { public readonly renderer: CoreRenderer; public readonly root: CoreNode; public readonly boundsMargin: [number, number, number, number]; + public readonly defShaderCtr: BaseShaderController; + + /** + * Renderer Event Bus for the Stage to emit events onto + * + * @remarks + * In reality this is just the RendererMain instance, which is an EventEmitter. + * this allows us to directly emit events from the Stage to RendererMain + * without having to set up forwarding handlers. + */ + public readonly eventBus: EventEmitter; /// State deltaTime = 0; @@ -96,6 +110,7 @@ export class Stage extends EventEmitter { private fpsNumFrames = 0; private fpsElapsedTime = 0; private renderRequested = false; + private frameEventQueue: [name: string, payload: unknown][] = []; /// Debug data contextSpy: ContextSpy | null = null; @@ -104,23 +119,21 @@ export class Stage extends EventEmitter { * Stage constructor */ constructor(readonly options: StageOptions) { - super(); const { canvas, clearColor, - rootId, - debug, appWidth, appHeight, boundsMargin, enableContextSpy, numImageWorkers, - txMemByteThreshold, + textureMemory, renderMode, } = options; + this.eventBus = options.eventBus; this.txManager = new CoreTextureManager(numImageWorkers); - this.txMemManager = new TextureMemoryManager(txMemByteThreshold); + this.txMemManager = new TextureMemoryManager(this, textureMemory); this.shManager = new CoreShaderManager(); this.animationManager = new AnimationManager(); this.contextSpy = enableContextSpy ? new ContextSpy() : null; @@ -133,15 +146,6 @@ export class Stage extends EventEmitter { } this.boundsMargin = bm; - if (debug?.monitorTextureCache) { - setInterval(() => { - assertTruthy(this.txManager); - const debugInfo = this.txManager.getDebugInfo(); - console.log('Texture ID Cache Size: ', debugInfo.idCacheSize); - console.log('Texture Key Cache Size: ', debugInfo.keyCacheSize); - }, 1000); - } - const rendererOptions: CoreRendererOptions = { stage: this, canvas, @@ -160,6 +164,7 @@ export class Stage extends EventEmitter { } else { this.renderer = new WebGlCoreRenderer(rendererOptions); } + this.defShaderCtr = this.renderer.getDefShaderCtr(); setPremultiplyMode(renderMode); // Must do this after renderer is created @@ -178,7 +183,6 @@ export class Stage extends EventEmitter { // create root node const rootNode = new CoreNode(this, { - id: rootId, x: 0, y: 0, width: appWidth, @@ -208,10 +212,11 @@ export class Stage extends EventEmitter { rotation: 0, parent: null, texture: null, - textureOptions: null, - shader: null, - shaderProps: null, + textureOptions: {}, + shader: this.defShaderCtr, rtt: false, + src: null, + scale: 1, }); this.root = rootNode; @@ -222,6 +227,24 @@ export class Stage extends EventEmitter { } } + updateFrameTime() { + const newFrameTime = getTimeStamp(); + this.lastFrameTime = this.currentFrameTime; + this.currentFrameTime = newFrameTime; + this.deltaTime = !this.lastFrameTime + ? 100 / 6 + : newFrameTime - this.lastFrameTime; + this.txManager.frameTime = newFrameTime; + this.txMemManager.frameTime = newFrameTime; + + // This event is emitted at the beginning of the frame (before any updates + // or rendering), so no need to to use `stage.queueFrameEvent` here. + this.eventBus.emit('frameTick', { + time: this.currentFrameTime, + delta: this.deltaTime, + }); + } + /** * Update animations */ @@ -230,17 +253,6 @@ export class Stage extends EventEmitter { if (!this.root) { return; } - this.lastFrameTime = this.currentFrameTime; - this.currentFrameTime = getTimeStamp(); - - this.deltaTime = !this.lastFrameTime - ? 100 / 6 - : this.currentFrameTime - this.lastFrameTime; - - this.emit('frameTick', { - time: this.currentFrameTime, - delta: this.deltaTime, - }); // step animation animationManager.update(this.deltaTime); } @@ -267,9 +279,9 @@ export class Stage extends EventEmitter { // Reset render operations and clear the canvas renderer.reset(); - // Check if we need to garbage collect - if (renderer.txMemManager.gcRequested) { - renderer.txMemManager.gc(); + // Check if we need to cleanup textures + if (this.txMemManager.criticalCleanupRequested) { + this.txMemManager.cleanup(); } // If we have RTT nodes draw them first @@ -292,6 +304,40 @@ export class Stage extends EventEmitter { } } + /** + * Queue an event to be emitted after the current/next frame is rendered + * + * @remarks + * When we are operating in the context of the render loop, we may want to + * emit events that are related to the current frame. However, we generally do + * NOT want to emit events directly in the middle of the render loop, since + * this could enable event handlers to modify the scene graph and cause + * unexpected behavior. Instead, we queue up events to be emitted and then + * flush the queue after the frame has been rendered. + * + * @param name + * @param data + */ + queueFrameEvent(name: string, data: unknown) { + this.frameEventQueue.push([name, data]); + } + + /** + * Emit all queued frame events + * + * @remarks + * This method should be called after the frame has been rendered to emit + * all events that were queued during the frame. + * + * See {@link queueFrameEvent} for more information. + */ + flushFrameEvents() { + for (const [name, data] of this.frameEventQueue) { + this.eventBus.emit(name, data); + } + this.frameEventQueue = []; + } + calculateFps() { // If there's an FPS update interval, emit the FPS update event // when the specified interval has elapsed. @@ -305,7 +351,7 @@ export class Stage extends EventEmitter { ); this.fpsNumFrames = 0; this.fpsElapsedTime = 0; - this.emit('fpsUpdate', { + this.queueFrameEvent('fpsUpdate', { fps, contextSpyData: this.contextSpy?.getData() ?? null, } satisfies FpsUpdatePayload); @@ -407,4 +453,115 @@ export class Stage extends EventEmitter { // the covariant state argument in the setter method map return resolvedTextRenderer as unknown as TextRenderer; } + + /** + * Create a shader controller instance + * + * @param type + * @param props + * @returns + */ + createShaderCtr( + type: keyof ShaderMap, + props: Record, + ): BaseShaderController { + return this.shManager.loadShader(type, props); + } + + createNode(props: Partial) { + const resolvedProps = this.resolveNodeDefaults(props); + return new CoreNode(this, resolvedProps); + } + + createTextNode(props: Partial) { + const fontSize = props.fontSize ?? 16; + const resolvedProps = { + ...this.resolveNodeDefaults(props), + text: props.text ?? '', + textRendererOverride: props.textRendererOverride ?? null, + fontSize, + fontFamily: props.fontFamily ?? 'sans-serif', + fontStyle: props.fontStyle ?? 'normal', + fontWeight: props.fontWeight ?? 'normal', + fontStretch: props.fontStretch ?? 'normal', + textAlign: props.textAlign ?? 'left', + contain: props.contain ?? 'none', + scrollable: props.scrollable ?? false, + scrollY: props.scrollY ?? 0, + offsetY: props.offsetY ?? 0, + letterSpacing: props.letterSpacing ?? 0, + lineHeight: props.lineHeight, // `undefined` is a valid value + maxLines: props.maxLines ?? 0, + textBaseline: props.textBaseline ?? 'alphabetic', + verticalAlign: props.verticalAlign ?? 'middle', + overflowSuffix: props.overflowSuffix ?? '...', + debug: props.debug ?? {}, + shaderProps: null, + }; + + return new CoreTextNode(this, resolvedProps); + } + + /** + * Resolves the default property values for a Node + * + * @remarks + * This method is used internally by the RendererMain to resolve the default + * property values for a Node. It is exposed publicly so that it can be used + * by Core Driver implementations. + * + * @param props + * @returns + */ + protected resolveNodeDefaults(props: Partial): CoreNodeProps { + const color = props.color ?? 0xffffffff; + const colorTl = props.colorTl ?? props.colorTop ?? props.colorLeft ?? color; + const colorTr = + props.colorTr ?? props.colorTop ?? props.colorRight ?? color; + const colorBl = + props.colorBl ?? props.colorBottom ?? props.colorLeft ?? color; + const colorBr = + props.colorBr ?? props.colorBottom ?? props.colorRight ?? color; + const data = santizeCustomDataMap(props.data ?? {}); + + return { + x: props.x ?? 0, + y: props.y ?? 0, + width: props.width ?? 0, + height: props.height ?? 0, + alpha: props.alpha ?? 1, + autosize: props.autosize ?? false, + clipping: props.clipping ?? false, + color, + colorTop: props.colorTop ?? color, + colorBottom: props.colorBottom ?? color, + colorLeft: props.colorLeft ?? color, + colorRight: props.colorRight ?? color, + colorBl, + colorBr, + colorTl, + colorTr, + zIndex: props.zIndex ?? 0, + zIndexLocked: props.zIndexLocked ?? 0, + parent: props.parent ?? null, + texture: props.texture ?? null, + textureOptions: props.textureOptions ?? {}, + shader: props.shader ?? this.defShaderCtr, + // Since setting the `src` will trigger a texture load, we need to set it after + // we set the texture. Otherwise, problems happen. + src: props.src ?? null, + scale: props.scale ?? null, + scaleX: props.scaleX ?? props.scale ?? 1, + scaleY: props.scaleY ?? props.scale ?? 1, + mount: props.mount ?? 0, + mountX: props.mountX ?? props.mount ?? 0, + mountY: props.mountY ?? props.mount ?? 0, + pivot: props.pivot ?? 0.5, + pivotX: props.pivotX ?? props.pivot ?? 0.5, + pivotY: props.pivotY ?? props.pivot ?? 0.5, + rotation: props.rotation ?? 0, + rtt: props.rtt ?? false, + data: data, + }; + } } diff --git a/src/core/TextureMemoryManager.ts b/src/core/TextureMemoryManager.ts index c59e15c9..765136d8 100644 --- a/src/core/TextureMemoryManager.ts +++ b/src/core/TextureMemoryManager.ts @@ -16,53 +16,262 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { CoreContextTexture } from './renderers/CoreContextTexture.js'; +import type { Stage } from './Stage.js'; +import type { Texture } from './textures/Texture.js'; +import { bytesToMb } from './utils.js'; +export interface TextureMemoryManagerSettings { + /** + * Critical Threshold (in bytes) + * + * @remarks + * When the amount of memory used by textures exceeds this threshold, + * the Renderer will immediately trigger a Texture Cleanup towards the + * Target Threshold Level. + * + * When set to `0`, the Texture Memory Manager is disabled. + * + * @defaultValue `124e6` (118 MB) + */ + criticalThreshold: number; + + /** + * Target Threshold Level (as fraction of Critical Threshold) + * + * @remarks + * This value is the fractional level of the Critical Threshold that the + * Texture Memory Manager will attempt to maintain by cleaning up textures. + * The Texture Memory Manager will attempt to keep the memory usage below + * this level by freeing up non-renderable textures. + * + * Valid Range: 0.0 - 1.0 + * + * @defaultValue `0.5` + */ + targetThresholdLevel: number; + + /** + * Interval between Texture Cleanups (in milliseconds) + * + * @remarks + * Texture Memory Manager will perform a Texture Cleanup no more + * frequently than this interval generally when the scene becomes idle. + * + * @defaultValue `30,000` (30 seconds) + */ + cleanupInterval: number; + + /** + * Whether or not to log debug information + * + * @defaultValue `false` + */ + debugLogging: boolean; +} + +export interface MemoryInfo { + criticalThreshold: number; + targetThreshold: number; + renderableMemUsed: number; + memUsed: number; + renderableTexturesLoaded: number; + loadedTextures: number; +} + +/** + * LRU (Least Recently Used) style memory manager for textures + * + * @remarks + * This class is responsible for managing the memory usage of textures + * in the Renderer. It keeps track of the memory used by each texture + * and triggers a cleanup when the memory usage exceeds a critical + * threshold (`criticalThreshold`). + * + * The cleanup process will free up non-renderable textures until the + * memory usage is below a target threshold (`targetThresholdLevel`). + * + * The memory manager's clean up process will also be triggered when the + * scene is idle for a certain amount of time (`cleanupInterval`). + */ export class TextureMemoryManager { private memUsed = 0; - private textures: Map = new Map(); - private threshold: number; - public gcRequested = false; - + private loadedTextures: Map = new Map(); + private criticalThreshold: number; + private targetThreshold: number; + private cleanupInterval: number; + private debugLogging: boolean; + private lastCleanupTime = 0; + public criticalCleanupRequested = false; /** - * @param byteThreshold Number of texture bytes to trigger garbage collection + * The current frame time in milliseconds + * + * @remarks + * This is used to determine when to perform Idle Texture Cleanups. + * + * Set by stage via `updateFrameTime` method. */ - constructor(byteThreshold: number) { - this.threshold = byteThreshold; + public frameTime = 0; + + constructor(private stage: Stage, settings: TextureMemoryManagerSettings) { + const { criticalThreshold } = settings; + this.criticalThreshold = Math.round(criticalThreshold); + const targetFraction = Math.max( + 0, + Math.min(1, settings.targetThresholdLevel), + ); + this.targetThreshold = Math.round(criticalThreshold * targetFraction); + this.cleanupInterval = settings.cleanupInterval; + this.debugLogging = settings.debugLogging; + + if (settings.debugLogging) { + let lastMemUse = 0; + setInterval(() => { + if (lastMemUse !== this.memUsed) { + lastMemUse = this.memUsed; + console.log( + `[TextureMemoryManager] Memory used: ${bytesToMb( + this.memUsed, + )} mb / ${bytesToMb(this.criticalThreshold)} mb (${( + (this.memUsed / this.criticalThreshold) * + 100 + ).toFixed(1)}%)`, + ); + } + }, 1000); + } // If the threshold is 0, we disable the memory manager by replacing the // setTextureMemUse method with a no-op function. - if (byteThreshold === 0) { + if (criticalThreshold === 0) { // eslint-disable-next-line @typescript-eslint/no-empty-function this.setTextureMemUse = () => {}; } } - setTextureMemUse(ctxTexture: CoreContextTexture, byteSize: number) { - if (this.textures.has(ctxTexture)) { + setTextureMemUse(texture: Texture, byteSize: number) { + if (this.loadedTextures.has(texture)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.memUsed -= this.textures.get(ctxTexture)!; + this.memUsed -= this.loadedTextures.get(texture)!; } if (byteSize === 0) { - this.textures.delete(ctxTexture); + this.loadedTextures.delete(texture); return; } else { this.memUsed += byteSize; - this.textures.set(ctxTexture, byteSize); + this.loadedTextures.set(texture, byteSize); } - if (this.memUsed > this.threshold) { - this.gcRequested = true; + if (this.memUsed > this.criticalThreshold) { + this.criticalCleanupRequested = true; } } - gc() { - this.gcRequested = false; - this.textures.forEach((byteSize, ctxTexture) => { - if (!ctxTexture.renderable) { - ctxTexture.free(); + checkCleanup() { + return ( + this.criticalCleanupRequested || + (this.memUsed > this.targetThreshold && + this.frameTime - this.lastCleanupTime >= this.cleanupInterval) + ); + } + + cleanup() { + const critical = this.criticalCleanupRequested; + this.lastCleanupTime = this.frameTime; + this.criticalCleanupRequested = false; + + if (critical) { + this.stage.queueFrameEvent('criticalCleanup', { + memUsed: this.memUsed, + criticalThreshold: this.criticalThreshold, + }); + } + + if (this.debugLogging) { + console.log( + `[TextureMemoryManager] Cleaning up textures. Critical: ${critical}`, + ); + } + + /** + * Sort the loaded textures by renderability, then by last touch time. + * + * This will ensure that the array is ordered by the following: + * - Non-renderable textures, starting at the least recently rendered + * - Renderable textures, starting at the least recently rendered + */ + const textures = [...this.loadedTextures.keys()].sort( + (textureA, textureB) => { + const txARenderable = textureA.renderable; + const txBRenderable = textureB.renderable; + if (txARenderable === txBRenderable) { + return ( + textureA.lastRenderableChangeTime - + textureB.lastRenderableChangeTime + ); + } else if (txARenderable) { + return 1; + } else if (txBRenderable) { + return -1; + } + return 0; + }, + ); + + // Free non-renderable textures until we reach the target threshold + const memTarget = this.targetThreshold; + const txManager = this.stage.txManager; + for (const texture of textures) { + if (texture.renderable) { + // Stop at the first renderable texture (The rest are renderable because of the sort above) + // We don't want to free renderable textures because they will just likely be reloaded in the next frame + break; + } + texture.ctxTexture.free(); + txManager.removeTextureFromCache(texture); + if (this.memUsed <= memTarget) { + // Stop once we've freed enough textures to reach under the target threshold + break; } - }); + } + + if (this.memUsed >= this.criticalThreshold) { + this.stage.queueFrameEvent('criticalCleanupFailed', { + memUsed: this.memUsed, + criticalThreshold: this.criticalThreshold, + }); + console.warn( + `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, + ); + } + } + + /** + * Get the current texture memory usage information + * + * @remarks + * This method is for debugging purposes and returns information about the + * current memory usage of the textures in the Renderer. + */ + getMemoryInfo(): MemoryInfo { + let renderableTexturesLoaded = 0; + const renderableMemUsed = [...this.loadedTextures.keys()].reduce( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (acc, texture) => { + renderableTexturesLoaded += texture.renderable ? 1 : 0; + return ( + acc + (texture.renderable ? this.loadedTextures.get(texture)! : 0) + ); + }, + 0, + ); + return { + criticalThreshold: this.criticalThreshold, + targetThreshold: this.targetThreshold, + renderableMemUsed, + memUsed: this.memUsed, + renderableTexturesLoaded, + loadedTextures: this.loadedTextures.size, + }; } } diff --git a/src/core/animations/CoreAnimation.ts b/src/core/animations/CoreAnimation.ts index 29402826..29cb4706 100644 --- a/src/core/animations/CoreAnimation.ts +++ b/src/core/animations/CoreAnimation.ts @@ -17,8 +17,7 @@ * limitations under the License. */ -import type { CoreNode } from '../CoreNode.js'; -import type { INodeAnimatableProps } from '../../main-api/INode.js'; +import { type CoreNode, type CoreNodeAnimateProps } from '../CoreNode.js'; import { getTimingFunction } from '../utils.js'; import { mergeColorProgress } from '../../utils.js'; import { EventEmitter } from '../../common/EventEmitter.js'; @@ -33,26 +32,68 @@ export interface AnimationSettings { stopMethod: 'reverse' | 'reset' | false; } +type PropValues = { + start: number; + target: number; +}; +type PropValuesMap = Record>; + export class CoreAnimation extends EventEmitter { - public propStartValues: Partial = {}; - public restoreValues: Partial = {}; public settings: AnimationSettings; private progress = 0; private delayFor = 0; private timingFunction: (t: number) => number | undefined; - private propsList: Array; //fixme - aint got not time for this + + propValuesMap: PropValuesMap = {}; + dynPropValuesMap: PropValuesMap | undefined = undefined; constructor( private node: CoreNode, - private props: Partial, + private props: Partial, settings: Partial, ) { super(); - this.propStartValues = {}; - this.propsList = Object.keys(props) as Array; - this.propsList.forEach((propName) => { - this.propStartValues[propName] = node[propName]; - }); + + for (const key in props) { + if (key !== 'shaderProps') { + if (this.propValuesMap['props'] === undefined) { + this.propValuesMap['props'] = {}; + } + this.propValuesMap['props'][key] = { + start: node[key as keyof Omit], + target: props[ + key as keyof Omit + ] as number, + }; + } else if (node.shader.type !== 'DynamicShader') { + this.propValuesMap['shaderProps'] = {}; + for (const key in props.shaderProps) { + this.propValuesMap['shaderProps'][key] = { + start: node.shader.props[key] as number, + target: props.shaderProps[key] as number, + }; + } + } else { + const shaderPropKeys = Object.keys(props.shaderProps!); + const spLength = shaderPropKeys.length; + this.dynPropValuesMap = {}; + for (let j = 0; j < spLength; j++) { + const effectName = shaderPropKeys[j]!; + const effect = props.shaderProps![effectName]!; + this.dynPropValuesMap[effectName] = {}; + const effectProps = Object.entries(effect); + const eLength = effectProps.length; + + for (let k = 0; k < eLength; k++) { + const [key, value] = effectProps[k]!; + this.dynPropValuesMap[effectName]![key] = { + start: node.shader.props[effectName][key], + target: value, + }; + } + } + } + } const easing = settings.easing || 'linear'; const delay = settings.delay ?? 0; @@ -75,28 +116,82 @@ export class CoreAnimation extends EventEmitter { this.update(0); } + private restoreValues( + target: Record, + valueMap: Record, + ) { + const entries = Object.entries(valueMap); + const eLength = entries.length; + + for (let i = 0; i < eLength; i++) { + const [key, value] = entries[i]!; + target[key] = value.start; + } + } + restore() { this.reset(); - (Object.keys(this.props) as Array).forEach( - (propName) => { - this.node[propName] = this.propStartValues[propName] as number; - }, - ); + if (this.propValuesMap['props'] !== undefined) { + this.restoreValues( + this.node as unknown as Record, + this.propValuesMap['props'], + ); + } + if (this.propValuesMap['shaderProps'] !== undefined) { + this.restoreValues( + this.node.shader.props as Record, + this.propValuesMap['shaderProps'], + ); + } + + if (this.dynPropValuesMap !== undefined) { + const dynEntries = Object.keys(this.dynPropValuesMap); + const dynEntriesL = dynEntries.length; + if (dynEntriesL > 0) { + for (let i = 0; i < dynEntriesL; i++) { + const key = dynEntries[i]!; + this.restoreValues( + this.node.shader.props[key], + this.dynPropValuesMap[key]!, + ); + } + } + } + } + + private reverseValues(valueMap: Record) { + const entries = Object.entries(valueMap); + const eLength = entries.length; + + for (let i = 0; i < eLength; i++) { + const [key, value] = entries[i]!; + valueMap[key] = { + start: value.target, + target: value.start, + }; + } } reverse() { this.progress = 0; - (Object.keys(this.props) as Array).forEach( - (propName) => { - // set the start value to the current value - const startValue = this.props[propName]; - const endValue = this.propStartValues[propName] as number; - - // swap the start and end values - this.props[propName] = endValue; - this.propStartValues[propName] = startValue; - }, - ); + + if (this.propValuesMap['props'] !== undefined) { + this.reverseValues(this.propValuesMap['props']); + } + if (this.propValuesMap['shaderProps'] !== undefined) { + this.reverseValues(this.propValuesMap['shaderProps']); + } + + if (this.dynPropValuesMap !== undefined) { + const dynEntries = Object.keys(this.dynPropValuesMap); + const dynEntriesL = dynEntries.length; + if (dynEntriesL > 0) { + for (let i = 0; i < dynEntriesL; i++) { + const key = dynEntries[i]!; + this.reverseValues(this.dynPropValuesMap[key]!); + } + } + } // restore stop method if we are not looping if (!this.settings.loop) { @@ -108,6 +203,53 @@ export class CoreAnimation extends EventEmitter { return (this.timingFunction(p) || p) * (e - s) + s; } + updateValue( + propName: string, + propValue: number, + startValue: number, + easing: string | undefined, + ): number { + if (this.progress === 1) { + return propValue; + } + if (this.progress === 0) { + return startValue; + } + + const endValue = propValue; + if (propName.indexOf('color') !== -1) { + if (startValue === endValue) { + return startValue; + } + + if (easing) { + const easingProgressValue = + this.timingFunction(this.progress) || this.progress; + return mergeColorProgress(startValue, endValue, easingProgressValue); + } + return mergeColorProgress(startValue, endValue, this.progress); + } + + if (easing) { + return this.applyEasing(this.progress, startValue, endValue); + } + return startValue + (endValue - startValue) * this.progress; + } + + private updateValues( + target: Record, + valueMap: Record, + easing: string | undefined, + ) { + const entries = Object.entries(valueMap); + const eLength = entries.length; + + for (let i = 0; i < eLength; i++) { + const [key, value] = entries[i]!; + target[key] = this.updateValue(key, value.target, value.start, easing); + } + } + update(dt: number) { const { duration, loop, easing, stopMethod } = this.settings; const { delayFor } = this; @@ -153,51 +295,36 @@ export class CoreAnimation extends EventEmitter { } } - for (let i = 0; i < this.propsList.length; i++) { - const propName = this.propsList[i] as keyof INodeAnimatableProps; - const propValue = this.props[propName] as number; - const startValue = this.propStartValues[propName] as number; - const endValue = propValue; - - if (propName.indexOf('color') !== -1) { - // check if we have to change the color to begin with - if (startValue === endValue) { - this.node[propName] = startValue; - continue; - } + if (this.propValuesMap['props'] !== undefined) { + this.updateValues( + this.node as unknown as Record, + this.propValuesMap['props'], + easing, + ); + } + if (this.propValuesMap['shaderProps'] !== undefined) { + this.updateValues( + this.node.shader.props as Record, + this.propValuesMap['shaderProps'], + easing, + ); + } - if (easing) { - const easingProgressValue = - this.timingFunction(this.progress) || this.progress; - const easingColorValue = mergeColorProgress( - startValue, - endValue, - easingProgressValue, + if (this.dynPropValuesMap !== undefined) { + const dynEntries = Object.keys(this.dynPropValuesMap); + const dynEntriesL = dynEntries.length; + if (dynEntriesL > 0) { + for (let i = 0; i < dynEntriesL; i++) { + const key = dynEntries[i]!; + this.updateValues( + this.node.shader.props[key], + this.dynPropValuesMap[key]!, + easing, ); - this.node[propName] = easingColorValue; - continue; } - - this.node[propName] = mergeColorProgress( - startValue, - endValue, - this.progress, - ); - continue; } - - if (easing) { - this.node[propName] = this.applyEasing( - this.progress, - startValue, - endValue, - ); - continue; - } - - this.node[propName] = - startValue + (endValue - startValue) * this.progress; } + if (this.progress === 1) { this.emit('finished', {}); } diff --git a/src/core/lib/ImageWorker.ts b/src/core/lib/ImageWorker.ts index 92414b98..c212a7f3 100644 --- a/src/core/lib/ImageWorker.ts +++ b/src/core/lib/ImageWorker.ts @@ -20,6 +20,84 @@ import { type TextureData } from '../textures/Texture.js'; type MessageCallback = [(value: any) => void, (reason: any) => void]; +interface getImageReturn { + data: ImageBitmap; + premultiplyAlpha: boolean | null; +} + +/** + * Note that, within the createImageWorker function, we must only use ES5 code to keep it ES5-valid after babelifying, as + * the converted code of this section is converted to a blob and used as the js of the web worker thread. + * + * The createImageWorker function is a web worker that fetches an image from a URL and returns an ImageBitmap object. + * The eslint @typescript rule is disabled for the entire function because the function is converted to a blob and used as the + * js of the web worker thread, so the typescript syntax is not valid in this context. + */ + +/* eslint-disable */ +function createImageWorker() { + function hasAlphaChannel(mimeType: string) { + return mimeType.indexOf('image/png') !== -1; + } + + function getImage( + src: string, + premultiplyAlpha: boolean | null, + ): Promise { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.responseType = 'blob'; + + xhr.onload = function () { + if (xhr.status !== 200) { + return reject(new Error('Failed to load image: ' + xhr.statusText)); + } + + var blob = xhr.response; + var withAlphaChannel = + premultiplyAlpha !== undefined + ? premultiplyAlpha + : hasAlphaChannel(blob.type); + + createImageBitmap(blob, { + premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none', + colorSpaceConversion: 'none', + imageOrientation: 'none', + }) + .then(function (data) { + resolve({ data, premultiplyAlpha: premultiplyAlpha }); + }) + .catch(function (error) { + reject(error); + }); + }; + + xhr.onerror = function () { + reject( + new Error('Network error occurred while trying to fetch the image.'), + ); + }; + + xhr.send(); + }); + } + + self.onmessage = (event) => { + var src = event.data.src; + var id = event.data.id; + var premultiplyAlpha = event.data.premultiplyAlpha; + + getImage(src, premultiplyAlpha) + .then(function (data) { + self.postMessage({ id: id, src: src, data: data }); + }) + .catch(function (error) { + self.postMessage({ id: id, src: src, error: error.message }); + }); + }; +} +/* eslint-enable */ export class ImageWorkerManager { imageWorkersEnabled = true; @@ -55,58 +133,7 @@ export class ImageWorkerManager { } private createWorkers(numWorkers = 1): Worker[] { - const workerCode = ` - function hasAlphaChannel(mimeType) { - return (mimeType.indexOf("image/png") !== -1); - } - - function getImage(src, premultiplyAlpha) { - return new Promise(function(resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.responseType = 'blob'; - - xhr.onload = function() { - if (xhr.status === 200) { - var blob = xhr.response; - var hasAlphaChannel = premultiplyAlpha !== undefined ? premultiplyAlpha : hasAlphaChannel(blob.type); - - createImageBitmap(blob, { - premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none', - colorSpaceConversion: 'none', - imageOrientation: 'none' - }).then(function(data) { - resolve({ data: data, premultiplyAlpha: premultiplyAlpha }); - }).catch(function(error) { - reject(error); - }); - } else { - reject(new Error('Failed to load image: ' + xhr.statusText)); - } - }; - - xhr.onerror = function() { - reject(new Error('Network error occurred while trying to fetch the image.')); - }; - - xhr.send(); - }); - } - - self.onmessage = (event) => { - var id = event.data.id; - var src = event.data.src; - var premultiplyAlpha = event.data.premultiplyAlpha; - - getImage(src, premultiplyAlpha) - .then(function(data) { - self.postMessage({ id: id, data: data }, [data.data]); - }) - .catch(function(error) { - self.postMessage({ id: id, error: error.message }); - }); - }; - `; + const workerCode = `(${createImageWorker.toString()})()`; const blob: Blob = new Blob([workerCode.replace('"use strict";', '')], { type: 'application/javascript', diff --git a/src/core/lib/utils.ts b/src/core/lib/utils.ts index 4e0f3773..d02bbc29 100644 --- a/src/core/lib/utils.ts +++ b/src/core/lib/utils.ts @@ -20,6 +20,7 @@ export const PROTOCOL_REGEX = /^(data|ftps?|https?):/; export type RGBA = [r: number, g: number, b: number, a: number]; + export const getNormalizedRgbaComponents = (rgba: number): RGBA => { const r = rgba >>> 24; const g = (rgba >>> 16) & 0xff; diff --git a/src/core/platform.ts b/src/core/platform.ts index 57596bc0..5f54236f 100644 --- a/src/core/platform.ts +++ b/src/core/platform.ts @@ -25,21 +25,28 @@ import type { Stage } from './Stage.js'; export const startLoop = (stage: Stage) => { let isIdle = false; const runLoop = () => { + stage.updateFrameTime(); stage.updateAnimations(); if (!stage.hasSceneUpdates()) { // We still need to calculate the fps else it looks like the app is frozen stage.calculateFps(); setTimeout(runLoop, 16.666666666666668); + if (!isIdle) { - stage.emit('idle'); + if (stage.txMemManager.checkCleanup()) { + stage.txMemManager.cleanup(); + } + stage.eventBus.emit('idle'); isIdle = true; } + stage.flushFrameEvents(); return; } isIdle = false; stage.drawFrame(); + stage.flushFrameEvents(); requestAnimationFrame(runLoop); }; requestAnimationFrame(runLoop); diff --git a/src/core/renderers/CoreContextTexture.ts b/src/core/renderers/CoreContextTexture.ts index c4ff84d3..94ecde3a 100644 --- a/src/core/renderers/CoreContextTexture.ts +++ b/src/core/renderers/CoreContextTexture.ts @@ -21,15 +21,22 @@ import type { TextureMemoryManager } from '../TextureMemoryManager.js'; import type { Texture } from '../textures/Texture.js'; export abstract class CoreContextTexture { - readonly memManager: TextureMemoryManager; readonly textureSource: Texture; + private memManager: TextureMemoryManager; constructor(memManager: TextureMemoryManager, textureSource: Texture) { this.memManager = memManager; this.textureSource = textureSource; } + protected setTextureMemUse(byteSize: number): void { + this.memManager.setTextureMemUse(this.textureSource, byteSize); + } + abstract load(): void; abstract free(): void; - abstract get renderable(): boolean; + + get renderable(): boolean { + return this.textureSource.renderable; + } } diff --git a/src/core/renderers/CoreRenderer.ts b/src/core/renderers/CoreRenderer.ts index 840ea9e4..aa0e3538 100644 --- a/src/core/renderers/CoreRenderer.ts +++ b/src/core/renderers/CoreRenderer.ts @@ -18,6 +18,7 @@ */ import type { Dimensions } from '../../common/CommonTypes.js'; +import type { BaseShaderController } from '../../main-api/ShaderController.js'; import type { CoreNode } from '../CoreNode.js'; import type { CoreShaderManager } from '../CoreShaderManager.js'; import type { @@ -27,8 +28,8 @@ import type { import type { Stage } from '../Stage.js'; import type { TextureMemoryManager } from '../TextureMemoryManager.js'; import type { ContextSpy } from '../lib/ContextSpy.js'; +import type { RenderCoords } from '../lib/RenderCoords.js'; import type { RectWithValid } from '../lib/utils.js'; -import { ColorTexture } from '../textures/ColorTexture.js'; import type { Texture } from '../textures/Texture.js'; import { CoreContextTexture } from './CoreContextTexture.js'; import type { CoreShader } from './CoreShader.js'; @@ -53,6 +54,7 @@ export interface QuadOptions { tb: number; tc: number; td: number; + renderCoords?: RenderCoords; rtt?: boolean; parentHasRenderTexture?: boolean; framebufferDimensions?: Dimensions; @@ -70,11 +72,16 @@ export interface CoreRendererOptions { contextSpy: ContextSpy | null; } +export interface BufferInfo { + totalUsed: number; + totalAvailable: number; +} + export abstract class CoreRenderer { public options: CoreRendererOptions; public mode: 'webgl' | 'canvas' | undefined; - protected stage: Stage; + readonly stage: Stage; //// Core Managers txManager: CoreTextureManager; @@ -100,4 +107,6 @@ export abstract class CoreRenderer { abstract renderRTTNodes(): void; abstract removeRTTNode(node: CoreNode): void; abstract renderToTexture(node: CoreNode): void; + abstract getBufferInfo(): BufferInfo | null; + abstract getDefShaderCtr(): BaseShaderController; } diff --git a/src/core/renderers/canvas/CanvasCoreRenderer.ts b/src/core/renderers/canvas/CanvasCoreRenderer.ts index a43cbe21..0fb82473 100644 --- a/src/core/renderers/canvas/CanvasCoreRenderer.ts +++ b/src/core/renderers/canvas/CanvasCoreRenderer.ts @@ -17,6 +17,7 @@ * limitations under the License. */ +import type { BaseShaderController } from '../../../main-api/ShaderController.js'; import type { CoreNode } from '../../CoreNode.js'; import type { CoreShaderManager } from '../../CoreShaderManager.js'; import { getRgbaComponents, type RGBA } from '../../lib/utils.js'; @@ -35,6 +36,7 @@ import { parseColor, type IParsedColor, } from './internal/ColorUtils.js'; +import { UnsupportedShader } from './shaders/UnsupportedShader.js'; export class CanvasCoreRenderer extends CoreRenderer { private context: CanvasRenderingContext2D; @@ -43,6 +45,8 @@ export class CanvasCoreRenderer extends CoreRenderer { private clearColor: RGBA | undefined; public renderToTextureActive = false; activeRttNode: CoreNode | null = null; + private defShaderCtr: BaseShaderController; + constructor(options: CoreRendererOptions) { super(options); @@ -54,6 +58,17 @@ export class CanvasCoreRenderer extends CoreRenderer { this.context = canvas.getContext('2d') as CanvasRenderingContext2D; this.pixelRatio = pixelRatio; this.clearColor = clearColor ? getRgbaComponents(clearColor) : undefined; + + // Stub for default shader controller since the canvas renderer does not + // (really) support the concept of a shader (yet) + this.defShaderCtr = { + type: 'DefaultShader', + props: {}, + shader: new UnsupportedShader('DefaultShader'), + getResolvedProps: () => () => { + return {}; + }, + }; } reset(): void { @@ -104,7 +119,7 @@ export class CanvasCoreRenderer extends CoreRenderer { texture = texture.parentTexture; } - ctxTexture = this.txManager.getCtxTexture(texture) as CanvasCoreTexture; + ctxTexture = texture.ctxTexture as CanvasCoreTexture; if (texture.state === 'freed') { ctxTexture.load(); return; @@ -209,6 +224,10 @@ export class CanvasCoreRenderer extends CoreRenderer { return this.shManager; } + override getDefShaderCtr(): BaseShaderController { + return this.defShaderCtr; + } + renderRTTNodes(): void { // noop } @@ -220,4 +239,7 @@ export class CanvasCoreRenderer extends CoreRenderer { renderToTexture(node: CoreNode): void { // noop } + getBufferInfo(): null { + return null; + } } diff --git a/src/core/renderers/canvas/CanvasCoreTexture.ts b/src/core/renderers/canvas/CanvasCoreTexture.ts index ef77635c..efcf9c4e 100644 --- a/src/core/renderers/canvas/CanvasCoreTexture.ts +++ b/src/core/renderers/canvas/CanvasCoreTexture.ts @@ -50,7 +50,7 @@ export class CanvasCoreTexture extends CoreContextTexture { this.image = undefined; this.tintCache = undefined; this.textureSource.setState('freed'); - this.memManager.setTextureMemUse(this, 0); + this.setTextureMemUse(0); } updateMemSize(): void { @@ -60,7 +60,7 @@ export class CanvasCoreTexture extends CoreContextTexture { const mult = this.tintCache ? 8 : 4; if (this.textureSource.dimensions) { const { width, height } = this.textureSource.dimensions; - this.memManager.setTextureMemUse(this, width * height * mult); + this.setTextureMemUse(width * height * mult); } } @@ -119,10 +119,6 @@ export class CanvasCoreTexture extends CoreContextTexture { return canvas; } - get renderable(): boolean { - return this.textureSource.renderable; - } - private async onLoadRequest(): Promise { const { data } = await this.textureSource.getTextureData(); // TODO: canvas from text renderer should be able to provide the canvas directly diff --git a/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts b/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts index 7ae1ea1b..90809c27 100644 --- a/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts +++ b/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts @@ -42,7 +42,7 @@ export class WebGlCoreCtxRenderTexture extends WebGlCoreCtxTexture { } override async onLoadRequest(): Promise { - const { glw, memManager } = this; + const { glw } = this; const nativeTexture = (this._nativeCtxTexture = this.createNativeCtxTexture()); const { width, height } = this.textureSource; @@ -60,7 +60,7 @@ export class WebGlCoreCtxRenderTexture extends WebGlCoreCtxTexture { ); // Update the texture memory manager - memManager.setTextureMemUse(this, width * height * 4); + this.setTextureMemUse(width * height * 4); // Bind the framebuffer glw.bindFramebuffer(this.framebuffer); diff --git a/src/core/renderers/webgl/WebGlCoreCtxTexture.ts b/src/core/renderers/webgl/WebGlCoreCtxTexture.ts index 6bde1ac0..c1eb9da5 100644 --- a/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +++ b/src/core/renderers/webgl/WebGlCoreCtxTexture.ts @@ -61,10 +61,6 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { return this._nativeCtxTexture; } - get renderable(): boolean { - return this.textureSource.renderable; - } - get w() { return this._w; } @@ -118,11 +114,11 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { * Called when the texture data needs to be loaded and uploaded to a texture */ async onLoadRequest(): Promise { - const { glw, memManager } = this; + const { glw } = this; // Set to a 1x1 transparent texture glw.texImage2D(0, glw.RGBA, 1, 1, 0, glw.RGBA, glw.UNSIGNED_BYTE, null); - memManager.setTextureMemUse(this, TRANSPARENT_TEXTURE_DATA.byteLength); + this.setTextureMemUse(TRANSPARENT_TEXTURE_DATA.byteLength); const textureData = await this.textureSource?.getTextureData(); // If the texture has been freed while loading, return early. @@ -153,7 +149,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { ); glw.texImage2D(0, glw.RGBA, glw.RGBA, glw.UNSIGNED_BYTE, data); - memManager.setTextureMemUse(this, width * height * 4); + this.setTextureMemUse(width * height * 4); // generate mipmaps for power-of-2 textures or in WebGL2RenderingContext if (glw.isWebGl2() || (isPowerOfTwo(width) && isPowerOfTwo(height))) { @@ -175,7 +171,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { glw.UNSIGNED_BYTE, TRANSPARENT_TEXTURE_DATA, ); - memManager.setTextureMemUse(this, TRANSPARENT_TEXTURE_DATA.byteLength); + this.setTextureMemUse(TRANSPARENT_TEXTURE_DATA.byteLength); } else if ('mipmaps' in textureData.data && textureData.data.mipmaps) { const { mipmaps, @@ -197,7 +193,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { glw.texParameteri(glw.TEXTURE_MAG_FILTER, glw.LINEAR); glw.texParameteri(glw.TEXTURE_MIN_FILTER, glw.LINEAR); - memManager.setTextureMemUse(this, view.byteLength); + this.setTextureMemUse(view.byteLength); } else { console.error( `WebGlCoreCtxTexture.onLoadRequest: Unexpected textureData returned`, @@ -227,10 +223,10 @@ export class WebGlCoreCtxTexture extends CoreContextTexture { if (!this._nativeCtxTexture) { return; } - const { glw, memManager } = this; + const { glw } = this; glw.deleteTexture(this._nativeCtxTexture); - memManager.setTextureMemUse(this, 0); + this.setTextureMemUse(0); this._nativeCtxTexture = null; } diff --git a/src/core/renderers/webgl/WebGlCoreRenderer.ts b/src/core/renderers/webgl/WebGlCoreRenderer.ts index 427466e2..13d2e21c 100644 --- a/src/core/renderers/webgl/WebGlCoreRenderer.ts +++ b/src/core/renderers/webgl/WebGlCoreRenderer.ts @@ -20,6 +20,7 @@ import { assertTruthy, createWebGLContext, hasOwn } from '../../../utils.js'; import { CoreRenderer, + type BufferInfo, type CoreRendererOptions, type QuadOptions, } from '../CoreRenderer.js'; @@ -50,6 +51,7 @@ import { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js'; import { RenderTexture } from '../../textures/RenderTexture.js'; import type { CoreNode } from '../../CoreNode.js'; import { WebGlCoreCtxRenderTexture } from './WebGlCoreCtxRenderTexture.js'; +import type { BaseShaderController } from '../../../main-api/ShaderController.js'; import { ImageTexture } from '../../textures/ImageTexture.js'; const WORDS_PER_QUAD = 24; @@ -68,9 +70,9 @@ export class WebGlCoreRenderer extends CoreRenderer { system: CoreWebGlSystem; //// Persistent data - quadBuffer: ArrayBuffer = new ArrayBuffer(1024 * 1024 * 4); - fQuadBuffer: Float32Array = new Float32Array(this.quadBuffer); - uiQuadBuffer: Uint32Array = new Uint32Array(this.quadBuffer); + quadBuffer: ArrayBuffer; + fQuadBuffer: Float32Array; + uiQuadBuffer: Uint32Array; renderOps: WebGlCoreRenderOp[] = []; //// Render Op / Buffer Filling State @@ -80,6 +82,7 @@ export class WebGlCoreRenderer extends CoreRenderer { activeRttNode: CoreNode | null = null; //// Default Shader + defShaderCtrl: BaseShaderController; defaultShader: WebGlCoreShader; quadBufferCollection: BufferCollection; @@ -88,6 +91,7 @@ export class WebGlCoreRenderer extends CoreRenderer { */ defaultTexture: Texture; + quadBufferUsage = 0; /** * Whether the renderer is currently rendering to a texture. */ @@ -95,6 +99,11 @@ export class WebGlCoreRenderer extends CoreRenderer { constructor(options: WebGlCoreRendererOptions) { super(options); + + this.quadBuffer = new ArrayBuffer(this.stage.options.quadBufferSize); + this.fQuadBuffer = new Float32Array(this.quadBuffer); + this.uiQuadBuffer = new Uint32Array(this.quadBuffer); + this.mode = 'webgl'; const { canvas, clearColor, bufferMemory } = options; @@ -102,7 +111,7 @@ export class WebGlCoreRenderer extends CoreRenderer { this.defaultTexture = new ColorTexture(this.txManager); // Mark the default texture as ALWAYS renderable - // This prevents it from ever being garbage collected. + // This prevents it from ever being cleaned up. // Fixes https://github.com/lightning-js/renderer/issues/262 this.defaultTexture.setRenderableOwner(this, true); @@ -128,7 +137,8 @@ export class WebGlCoreRenderer extends CoreRenderer { extensions: getWebGlExtensions(this.glw), }; this.shManager.renderer = this; - this.defaultShader = this.shManager.loadShader('DefaultShader').shader; + this.defShaderCtrl = this.shManager.loadShader('DefaultShader'); + this.defaultShader = this.defShaderCtrl.shader as WebGlCoreShader; const quadBuffer = glw.createBuffer(); assertTruthy(quadBuffer); const stride = 6 * Float32Array.BYTES_PER_ELEMENT; @@ -235,6 +245,7 @@ export class WebGlCoreRenderer extends CoreRenderer { tb, tc, td, + renderCoords, rtt: renderToTexture, parentHasRenderTexture, framebufferDimensions, @@ -281,7 +292,12 @@ export class WebGlCoreRenderer extends CoreRenderer { } const flipX = textureOptions?.flipX ?? false; - const flipY = textureOptions?.flipY ?? false; + let flipY = textureOptions?.flipY ?? false; + + // always flip flipY for render textures + if (texture instanceof RenderTexture) { + flipY = !flipY; + } let texCoordX1 = 0; let texCoordY1 = 0; @@ -333,16 +349,46 @@ export class WebGlCoreRenderer extends CoreRenderer { [texCoordY1, texCoordY2] = [texCoordY2, texCoordY1]; } - const { txManager } = this.stage; - const ctxTexture = txManager.getCtxTexture(texture); + const ctxTexture = texture.ctxTexture; assertTruthy(ctxTexture instanceof WebGlCoreCtxTexture); const textureIdx = this.addTexture(ctxTexture, bufferIdx); curRenderOp = this.curRenderOp; assertTruthy(curRenderOp); + if (renderCoords) { + const { x1, y1, x2, y2, x3, y3, x4, y4 } = renderCoords; + // Upper-Left + fQuadBuffer[bufferIdx++] = x1; // vertexX + fQuadBuffer[bufferIdx++] = y1; // vertexY + fQuadBuffer[bufferIdx++] = texCoordX1; // texCoordX + fQuadBuffer[bufferIdx++] = texCoordY1; // texCoordY + uiQuadBuffer[bufferIdx++] = colorTl; // color + fQuadBuffer[bufferIdx++] = textureIdx; // texIndex + + // Upper-Right + fQuadBuffer[bufferIdx++] = x2; + fQuadBuffer[bufferIdx++] = y2; + fQuadBuffer[bufferIdx++] = texCoordX2; + fQuadBuffer[bufferIdx++] = texCoordY1; + uiQuadBuffer[bufferIdx++] = colorTr; + fQuadBuffer[bufferIdx++] = textureIdx; - // render quad advanced - if (tb !== 0 || tc !== 0) { + // Lower-Left + fQuadBuffer[bufferIdx++] = x4; + fQuadBuffer[bufferIdx++] = y4; + fQuadBuffer[bufferIdx++] = texCoordX1; + fQuadBuffer[bufferIdx++] = texCoordY2; + uiQuadBuffer[bufferIdx++] = colorBl; + fQuadBuffer[bufferIdx++] = textureIdx; + + // Lower-Right + fQuadBuffer[bufferIdx++] = x3; + fQuadBuffer[bufferIdx++] = y3; + fQuadBuffer[bufferIdx++] = texCoordX2; + fQuadBuffer[bufferIdx++] = texCoordY2; + uiQuadBuffer[bufferIdx++] = colorBr; + fQuadBuffer[bufferIdx++] = textureIdx; + } else if (tb !== 0 || tc !== 0) { // Upper-Left fQuadBuffer[bufferIdx++] = tx; // vertexX fQuadBuffer[bufferIdx++] = ty; // vertexY @@ -412,7 +458,6 @@ export class WebGlCoreRenderer extends CoreRenderer { uiQuadBuffer[bufferIdx++] = colorBr; fQuadBuffer[bufferIdx++] = textureIdx; } - // Update the length of the current render op curRenderOp.length += WORDS_PER_QUAD; curRenderOp.numQuads++; @@ -575,6 +620,7 @@ export class WebGlCoreRenderer extends CoreRenderer { } renderOp.draw(); }); + this.quadBufferUsage = this.curBufferIdx * arr.BYTES_PER_ELEMENT; } renderToTexture(node: CoreNode) { @@ -605,7 +651,7 @@ export class WebGlCoreRenderer extends CoreRenderer { this.activeRttNode = node; assertTruthy(node.texture, 'RTT node missing texture'); - const ctxTexture = txManager.getCtxTexture(node.texture); + const ctxTexture = node.texture.ctxTexture; assertTruthy(ctxTexture instanceof WebGlCoreCtxRenderTexture); this.renderToTextureActive = true; @@ -655,4 +701,16 @@ export class WebGlCoreRenderer extends CoreRenderer { } this.rttNodes.splice(index, 1); } + + getBufferInfo(): BufferInfo | null { + const bufferInfo: BufferInfo = { + totalAvailable: this.stage.options.quadBufferSize, + totalUsed: this.quadBufferUsage, + }; + return bufferInfo; + } + + override getDefShaderCtr(): BaseShaderController { + return this.defShaderCtrl; + } } diff --git a/src/core/renderers/webgl/shaders/DynamicShader.ts b/src/core/renderers/webgl/shaders/DynamicShader.ts index 288d481c..47bfa2ca 100644 --- a/src/core/renderers/webgl/shaders/DynamicShader.ts +++ b/src/core/renderers/webgl/shaders/DynamicShader.ts @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { ExtractProps } from '../../../CoreTextureManager.js'; import type { WebGlCoreRenderer } from '../WebGlCoreRenderer.js'; import { WebGlCoreShader, @@ -25,80 +24,85 @@ import { } from '../WebGlCoreShader.js'; import type { UniformInfo } from '../internal/ShaderUtils.js'; import type { WebGlCoreCtxTexture } from '../WebGlCoreCtxTexture.js'; -import { ShaderEffect } from './effects/ShaderEffect.js'; +import { + ShaderEffect, + type EffectDescUnion, + type ShaderEffectUniform, + type ShaderEffectValueMap, + type BaseEffectDesc, +} from './effects/ShaderEffect.js'; import type { EffectMap } from '../../../CoreShaderManager.js'; -import memize from 'memize'; - -/** - * Allows the `keyof EffectMap` to be mapped over and form an discriminated - * union of all the EffectDescs structures individually. - * - * @remarks - * When used like the following: - * ``` - * MapEffectDescs[] - * ``` - * The resultant type will be a discriminated union like so: - * ``` - * ( - * { - * type: 'radius', - * props?: { - * radius?: number | number[]; - * } - * } | - * { - * type: 'border', - * props?: { - * width?: number; - * color?: number; - * } - * } | - * // ... - * )[] - * ``` - * Which means TypeScript will now base its type checking on the `type` field - * and will know exactly what the `props` field should be based on the `type` - * field. - */ -type MapEffectDescs = T extends keyof EffectMap - ? SpecificEffectDesc - : never; - -export type EffectDesc = MapEffectDescs; +import { assertTruthy } from '../../../../utils.js'; export interface DynamicShaderProps extends DimensionsShaderProp, AlphaShaderProp { - effects?: EffectDesc[]; -} - -export interface SpecificEffectDesc< - FxType extends keyof EffectMap = keyof EffectMap, -> { - type: FxType; - props?: ExtractProps; + effects?: EffectDescUnion[]; } -const effectCache = new Map(); +const effectCache = new Map(); const getResolvedEffect = ( - effects: EffectDesc[] | undefined, - effectContructors: Partial | undefined, -): EffectDesc[] => { + effects: BaseEffectDesc[], + effectContructors: Partial, +): BaseEffectDesc[] => { const key = JSON.stringify(effects); if (effectCache.has(key)) { return effectCache.get(key)!; } + effects = effects ?? []; + const resolvedEffects = []; + const effectsLength = effects.length; + let i = 0; + for (; i < effectsLength; i++) { + const { name, type, props } = effects[i] as BaseEffectDesc; + const resolvedEffect = { + name, + type, + props: {} as Record, + }; + + const effectConstructor = effectContructors[type]!; + const defaultPropValues = effectConstructor.resolveDefaults(props); + const uniforms = effectConstructor.uniforms; + const uniformKeys = Object.keys(uniforms); + const uniformsLength = uniformKeys.length; + let j = 0; + for (; j < uniformsLength; j++) { + const key = uniformKeys[j]!; + const uniform = uniforms[key]!; + + const result: ShaderEffectValueMap = { + value: defaultPropValues[key] as ShaderEffectUniform['value'], + programValue: undefined, + updateOnBind: uniform.updateOnBind || false, + hasValidator: uniform.validator !== undefined, + hasProgramValueUpdater: uniform.updateProgramValue !== undefined, + }; + + const validatedValue = + (result.hasValidator && + uniform.validator!(defaultPropValues[key], defaultPropValues)) || + defaultPropValues[key]; + + if (defaultPropValues[key] !== validatedValue) { + result.validatedValue = validatedValue as number | number[]; + } - const value = (effects ?? []).map((effect) => ({ - type: effect.type, - props: effectContructors![effect.type]!.resolveDefaults( - (effect.props || {}) as any, - ), - })) as EffectDesc[]; + if (result.hasProgramValueUpdater) { + uniform.updateProgramValue!(result); + } - effectCache.set(key, value); - return value; + if (result.programValue === undefined) { + result.programValue = result.value as number; + } + + resolvedEffect.props[key] = result; + } + resolvedEffects.push(resolvedEffect); + } + + effectCache.set(key, resolvedEffects); + return resolvedEffects; }; export class DynamicShader extends WebGlCoreShader { @@ -130,8 +134,6 @@ export class DynamicShader extends WebGlCoreShader { this.effects = shader.effects as Array< InstanceType >; - - this.calculateProps = memize(this.calculateProps.bind(this)); } override bindTextures(textures: WebGlCoreCtxTexture[]) { @@ -140,34 +142,29 @@ export class DynamicShader extends WebGlCoreShader { glw.bindTexture(textures[0]!.ctxTexture); } - private calculateProps(effects: EffectDesc[]) { - const regEffects = this.renderer.shManager.getRegisteredEffects(); - const results: { name: string; value: unknown }[] = []; - effects?.forEach((eff, index) => { - const effect = this.effects[index]!; - const fxClass = regEffects[effect.name as keyof EffectMap]!; - const props = eff.props ?? {}; - const uniInfo = effect.uniformInfo; - Object.keys(props).forEach((p) => { - const fxProp = fxClass.uniforms[p]!; - const propInfo = uniInfo[p]!; - let value = fxProp.validator - ? fxProp.validator(props[p], props) - : props[p]; - if (Array.isArray(value)) { - value = new Float32Array(value); - } - results.push({ name: propInfo.name, value }); - }); - }); - return results; - } - protected override bindProps(props: Required): void { - const results = this.calculateProps(props.effects); - results.forEach((r) => { - this.setUniform(r.name, r.value); - }); + const effects = props.effects; + const effectsL = effects.length; + let i = 0; + for (; i < effectsL; i++) { + const effect = effects[i]! as Record; + const uniformInfo = this.effects[i]!.uniformInfo; + const propKeys = Object.keys(effect.props); + const propsLength = propKeys.length; + let j = 0; + for (; j < propsLength; j++) { + const key = propKeys[j]!; + const prop = effect.props[key]!; + if (prop.updateOnBind === true) { + const uniform = + this.renderer.shManager.getRegisteredEffects()[ + effect.type as keyof EffectMap + ]?.uniforms[key]; + uniform?.updateProgramValue!(effect.props[key], props); + } + this.setUniform(uniformInfo[key]!.name, effect.props[key].programValue); + } + } } override canBatchShaderProps( @@ -192,7 +189,8 @@ export class DynamicShader extends WebGlCoreShader { for (const key in effectA.props) { if ( (effectB.props && !effectB.props[key]) || - effectA.props[key] !== effectB.props![key] + (effectA.props[key] as ShaderEffectValueMap).value !== + (effectB.props[key] as ShaderEffectValueMap).value ) { return false; } @@ -396,8 +394,12 @@ export class DynamicShader extends WebGlCoreShader { props: DynamicShaderProps, effectContructors?: Partial, ): Required { + assertTruthy(effectContructors); return { - effects: getResolvedEffect(props.effects, effectContructors), + effects: getResolvedEffect( + props.effects ?? [], + effectContructors, + ) as EffectDescUnion[], $dimensions: { width: 0, height: 0, diff --git a/src/core/renderers/webgl/shaders/RoundedRectangle.ts b/src/core/renderers/webgl/shaders/RoundedRectangle.ts index 88423449..f81bfe56 100644 --- a/src/core/renderers/webgl/shaders/RoundedRectangle.ts +++ b/src/core/renderers/webgl/shaders/RoundedRectangle.ts @@ -77,7 +77,10 @@ export class RoundedRectangle extends WebGlCoreShader { } protected override bindProps(props: Required): void { - this.setUniform('u_radius', props.radius); + const radiusFactor = + Math.min(props.$dimensions.width, props.$dimensions.height) / + (2.0 * props.radius); + this.setUniform('u_radius', props.radius * Math.min(radiusFactor, 1)); } override canBatchShaderProps( diff --git a/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.ts b/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.ts index fe04a72e..b1b27c26 100644 --- a/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.ts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -69,7 +69,7 @@ export class BorderBottomEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/BorderEffect.ts b/src/core/renderers/webgl/shaders/effects/BorderEffect.ts index dceecad0..77a5eb8b 100644 --- a/src/core/renderers/webgl/shaders/effects/BorderEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/BorderEffect.ts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -69,7 +69,7 @@ export class BorderEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.ts b/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.ts index bbb32c4f..3663230b 100644 --- a/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.ts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -69,7 +69,7 @@ export class BorderLeftEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/BorderRightEffect.ts b/src/core/renderers/webgl/shaders/effects/BorderRightEffect.ts index ebff2224..3a6984f6 100644 --- a/src/core/renderers/webgl/shaders/effects/BorderRightEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/BorderRightEffect.ts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -69,7 +69,7 @@ export class BorderRightEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/BorderTopEffect.ts b/src/core/renderers/webgl/shaders/effects/BorderTopEffect.ts index 22aa33d5..3164e3bf 100644 --- a/src/core/renderers/webgl/shaders/effects/BorderTopEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/BorderTopEffect.ts @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -69,7 +69,7 @@ export class BorderTopEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/EffectUtils.ts b/src/core/renderers/webgl/shaders/effects/EffectUtils.ts index ef3dc3a5..62ca70bc 100644 --- a/src/core/renderers/webgl/shaders/effects/EffectUtils.ts +++ b/src/core/renderers/webgl/shaders/effects/EffectUtils.ts @@ -1,3 +1,6 @@ +import type { DynamicShaderProps } from '../DynamicShader.js'; +import type { ShaderEffectValueMap } from './ShaderEffect.js'; + /* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: @@ -18,9 +21,11 @@ */ export const colorToFloat32Array = (argb: number) => { const col = getRgbaComponentsNormalized(argb); - col[0] *= col[3]!; - col[1] *= col[3]!; - col[2] *= col[3]!; + /* eslint-disable @typescript-eslint/no-non-null-assertion -- getRgbaComponentsNormalized has a constant array size */ + col[0]! *= col[3]!; + col[1]! *= col[3]!; + col[2]! *= col[3]!; + /* eslint-enable */ return col; }; @@ -31,3 +36,124 @@ const getRgbaComponentsNormalized = (argb: number): number[] => { const a = (argb / 16777216) | 0; return [r / 255, g / 255, b / 255, a / 255]; }; + +export const updateShaderEffectColor = (values: ShaderEffectValueMap) => { + if (values.programValue === undefined) { + values.programValue = new Float32Array(4); + } + const rgba = values.value as number; + const floatArray = values.programValue as Float32Array; + floatArray[0] = (rgba >>> 24) / 255; + floatArray[1] = ((rgba >>> 16) & 0xff) / 255; + floatArray[2] = ((rgba >>> 8) & 0xff) / 255; + floatArray[3] = (rgba & 0xff) / 255; +}; + +export const updateFloat32ArrayLength2 = (values: ShaderEffectValueMap) => { + const validatedValue = (values.validatedValue || values.value) as number[]; + if (values.programValue instanceof Float32Array) { + const floatArray = values.programValue; + floatArray[0] = validatedValue[0]!; + floatArray[1] = validatedValue[1]!; + } else { + values.programValue = new Float32Array(validatedValue); + } +}; + +export const updateFloat32ArrayLength4 = (values: ShaderEffectValueMap) => { + const validatedValue = (values.validatedValue || values.value) as number[]; + if (values.programValue instanceof Float32Array) { + const floatArray = values.programValue; + floatArray[0] = validatedValue[0]!; + floatArray[1] = validatedValue[1]!; + floatArray[2] = validatedValue[1]!; + floatArray[3] = validatedValue[1]!; + } else { + values.programValue = new Float32Array(validatedValue); + } +}; + +export const updateFloat32ArrayLengthN = (values: ShaderEffectValueMap) => { + const validatedValue = (values.validatedValue || values.value) as number[]; + if (values.programValue instanceof Float32Array) { + const len = validatedValue.length; + const programValue = values.programValue; + for (let i = 0; i < len; i++) { + programValue[i] = validatedValue[i]!; + } + } else { + values.programValue = new Float32Array(validatedValue); + } +}; + +export const validateArrayLength4 = (value: number | number[]): number[] => { + const isArray = Array.isArray(value); + if (!isArray) { + return [value, value, value, value]; + } else if (isArray && value.length === 4) { + return value; + } else if (isArray && value.length === 2) { + return [value[0]!, value[1]!, value[0]!, value[1]!]; + } else if (isArray && value.length === 3) { + return [value[0]!, value[1]!, value[2]!, value[0]!]; + } + return [value[0]!, value[0]!, value[0]!, value[0]!]; +}; + +export const updateWebSafeRadius = ( + values: ShaderEffectValueMap, + shaderProps?: DynamicShaderProps, +) => { + if (values.programValue === undefined) { + values.programValue = new Float32Array(4); + } + const programValue = values.programValue as Float32Array; + const validatedValue = (values.validatedValue || values.value) as number[]; + if (shaderProps === undefined && values.$dimensions === undefined) { + programValue[0] = validatedValue[0]!; + programValue[1] = validatedValue[1]!; + programValue[2] = validatedValue[2]!; + programValue[3] = validatedValue[3]!; + return; + } + + let storedDimensions = values.$dimensions; + if (shaderProps !== undefined) { + const { $dimensions } = shaderProps; + if ( + storedDimensions !== undefined && + (storedDimensions.width === $dimensions!.width || + storedDimensions.height === $dimensions!.height) + ) { + return; + } + if (storedDimensions === undefined) { + storedDimensions = { + width: $dimensions?.width as number, + height: $dimensions?.height as number, + }; + values.$dimensions = storedDimensions; + } + } + + const { width, height } = storedDimensions!; + const [r0, r1, r2, r3] = validatedValue; + const factor = Math.min( + Math.min( + Math.min( + width / Math.max(width, r0! + r1!), + width / Math.max(width, r2! + r3!), + ), + Math.min( + height / Math.max(height, r0! + r2!), + height / Math.max(height, r1! + r3!), + ), + ), + 1, + ); + + programValue[0] = r0! * factor; + programValue[1] = r1! * factor; + programValue[2] = r2! * factor; + programValue[3] = r3! * factor; +}; diff --git a/src/core/renderers/webgl/shaders/effects/FadeOutEffect.ts b/src/core/renderers/webgl/shaders/effects/FadeOutEffect.ts index 16a56ef6..55b48af9 100644 --- a/src/core/renderers/webgl/shaders/effects/FadeOutEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/FadeOutEffect.ts @@ -16,10 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + updateFloat32ArrayLength4, + validateArrayLength4, +} from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, type ShaderEffectUniforms, + type ShaderEffectValueMap, } from './ShaderEffect.js'; /** @@ -63,21 +68,8 @@ export class FadeOutEffect extends ShaderEffect { value: 0, method: 'uniform4fv', type: 'vec4', - validator: (value: number | number[]) => { - let r = value; - if (Array.isArray(r)) { - if (r.length === 2) { - r = [r[0], r[1], r[0], r[1]] as number[]; - } else if (r.length === 3) { - r = [r[0], r[1], r[2], r[0]] as number[]; - } else if (r.length !== 4) { - r = [r[0], r[0], r[0], r[0]] as number[]; - } - } else if (typeof r === 'number') { - r = [r, r, r, r]; - } - return r; - }, + validator: validateArrayLength4, + updateProgramValue: updateFloat32ArrayLength4, }, }; diff --git a/src/core/renderers/webgl/shaders/effects/GlitchEffect.ts b/src/core/renderers/webgl/shaders/effects/GlitchEffect.ts index 2ef2a7c7..7cf4d4e3 100644 --- a/src/core/renderers/webgl/shaders/effects/GlitchEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/GlitchEffect.ts @@ -84,8 +84,11 @@ export class GlitchEffect extends ShaderEffect { time: { value: 0, method: 'uniform1f', - validator: (value: number) => { - return (Date.now() - value) % 1000; + updateOnBind: true, + updateProgramValue: (values) => { + const value = (values.value = + (Date.now() - (values.value as number)) % 1000); + values.programValue = value; }, type: 'float', }, diff --git a/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts b/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts index dab1eed0..12fcb994 100644 --- a/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts @@ -16,10 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { DynamicShaderProps } from '../DynamicShader.js'; +import { updateWebSafeRadius, validateArrayLength4 } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, type ShaderEffectUniforms, + type ShaderEffectValueMap, } from './ShaderEffect.js'; /** @@ -103,21 +106,9 @@ export class HolePunchEffect extends ShaderEffect { value: 0, method: 'uniform4fv', type: 'vec4', - validator: (value: number | number[]) => { - let r = value; - if (Array.isArray(r)) { - if (r.length === 2) { - r = [r[0], r[1], r[0], r[1]] as number[]; - } else if (r.length === 3) { - r = [r[0], r[1], r[2], r[0]] as number[]; - } else if (r.length !== 4) { - r = [r[0], r[0], r[0], r[0]] as number[]; - } - } else if (typeof r === 'number') { - r = [r, r, r, r]; - } - return r; - }, + updateOnBind: true, + validator: validateArrayLength4, + updateProgramValue: updateWebSafeRadius, }, }; diff --git a/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts b/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts index 3077ba58..fc08e7e8 100644 --- a/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts @@ -17,6 +17,7 @@ * limitations under the License. */ import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateFloat32ArrayLengthN } from './EffectUtils.js'; import { type DefaultEffectProps, ShaderEffect, @@ -94,9 +95,12 @@ export class LinearGradientEffect extends ShaderEffect { colors: { value: 0xffffffff, validator: (rgbas: number[]): number[] => { - const cols = rgbas.map((rgbas) => getNormalizedRgbaComponents(rgbas)); - return cols.reduce((acc, val) => acc.concat(val), [] as number[]); + return rgbas.reduce( + (acc, val) => acc.concat(getNormalizedRgbaComponents(val)), + [] as number[], + ); }, + updateProgramValue: updateFloat32ArrayLengthN, size: (props: LinearGradientEffectProps) => props.colors!.length, method: 'uniform4fv', type: 'vec4', diff --git a/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts b/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts index d08d415f..5ee2eff6 100644 --- a/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts @@ -17,6 +17,10 @@ * limitations under the License. */ import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { + updateFloat32ArrayLength2, + updateFloat32ArrayLengthN, +} from './EffectUtils.js'; import { type DefaultEffectProps, ShaderEffect, @@ -105,15 +109,19 @@ export class RadialGradientEffect extends ShaderEffect { }, pivot: { value: [0.5, 0.5], + updateProgramValue: updateFloat32ArrayLength2, method: 'uniform2fv', type: 'vec2', }, colors: { value: 0xffffffff, validator: (rgbas: number[]): number[] => { - const cols = rgbas.map((rgbas) => getNormalizedRgbaComponents(rgbas)); - return cols.reduce((acc, val) => acc.concat(val), [] as number[]); + return rgbas.reduce( + (acc, val) => acc.concat(getNormalizedRgbaComponents(val)), + [] as number[], + ); }, + updateProgramValue: updateFloat32ArrayLengthN, size: (props: RadialGradientEffectProps) => props.colors!.length, method: 'uniform4fv', type: 'vec4', diff --git a/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts b/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts index 7c4c3284..238b7e15 100644 --- a/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts @@ -17,6 +17,7 @@ * limitations under the License. */ import { getNormalizedRgbaComponents } from '../../../../lib/utils.js'; +import { updateShaderEffectColor } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, @@ -132,7 +133,7 @@ export class RadialProgressEffect extends ShaderEffect { }, color: { value: 0xffffffff, - validator: (rgba): number[] => getNormalizedRgbaComponents(rgba), + updateProgramValue: updateShaderEffectColor, method: 'uniform4fv', type: 'vec4', }, diff --git a/src/core/renderers/webgl/shaders/effects/RadiusEffect.ts b/src/core/renderers/webgl/shaders/effects/RadiusEffect.ts index f2c86a4e..40e9b212 100644 --- a/src/core/renderers/webgl/shaders/effects/RadiusEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/RadiusEffect.ts @@ -16,10 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { DynamicShaderProps } from '../DynamicShader.js'; +import { updateWebSafeRadius, validateArrayLength4 } from './EffectUtils.js'; import { ShaderEffect, type DefaultEffectProps, type ShaderEffectUniforms, + type ShaderEffectValueMap, } from './ShaderEffect.js'; /** @@ -65,21 +68,9 @@ export class RadiusEffect extends ShaderEffect { value: 0, method: 'uniform4fv', type: 'vec4', - validator: (value: number | number[]) => { - let r = value; - if (Array.isArray(r)) { - if (r.length === 2) { - r = [r[0], r[1], r[0], r[1]] as number[]; - } else if (r.length === 3) { - r = [r[0], r[1], r[2], r[0]] as number[]; - } else if (r.length !== 4) { - r = [r[0], r[0], r[0], r[0]] as number[]; - } - } else if (typeof r === 'number') { - r = [r, r, r, r]; - } - return r; - }, + updateOnBind: true, + validator: validateArrayLength4, + updateProgramValue: updateWebSafeRadius, }, }; diff --git a/src/core/renderers/webgl/shaders/effects/ShaderEffect.ts b/src/core/renderers/webgl/shaders/effects/ShaderEffect.ts index 910275cb..8cb18fc0 100644 --- a/src/core/renderers/webgl/shaders/effects/ShaderEffect.ts +++ b/src/core/renderers/webgl/shaders/effects/ShaderEffect.ts @@ -1,20 +1,100 @@ +import type { EffectMap } from '../../../../CoreShaderManager.js'; +import type { ExtractProps } from '../../../../CoreTextureManager.js'; +import type { + AlphaShaderProp, + DimensionsShaderProp, +} from '../../WebGlCoreShader.js'; import type { UniformInfo, UniformMethodMap, } from '../../internal/ShaderUtils.js'; +export interface BaseEffectDesc { + name?: string; + type: keyof EffectMap; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + props: Record; +} + +export interface EffectDesc< + T extends { name?: string; type: keyof EffectMap } = { + name?: string; + type: keyof EffectMap; + }, +> extends BaseEffectDesc { + name?: T['name']; + type: T['type']; + props: ExtractProps; +} + +/** + * Allows the `keyof EffectMap` to be mapped over and form an discriminated + * union of all the EffectDescs structures individually. + * + * @remarks + * When used like the following: + * ``` + * MapEffectDescs[] + * ``` + * The resultant type will be a discriminated union like so: + * ``` + * ( + * { + * name: 'effect1', + * type: 'radius', + * props?: { + * radius?: number | number[]; + * } + * } | + * { + * name: 'effect2', + * type: 'border', + * props?: { + * width?: number; + * color?: number; + * } + * } | + * // ... + * )[] + * ``` + * Which means TypeScript will now base its type checking on the `type` field + * and will know exactly what the `props` field should be based on the `type` + * field. + */ +type MapEffectDescs = T extends keyof EffectMap + ? EffectDesc<{ type: T; name: string }> + : never; + +export type EffectDescUnion = MapEffectDescs; + export interface ShaderEffectUniform { value: number | number[] | boolean | string; type: string; method: keyof UniformMethodMap; name?: string; size?: (value: Record) => number; + updateOnBind?: boolean; + updateProgramValue?: ( + programValues: ShaderEffectValueMap, + shaderProps?: Record, + ) => void; validator?: ( value: any, props: Record, ) => number | number[] | number[][]; } +export interface ShaderEffectValueMap + extends DimensionsShaderProp, + AlphaShaderProp { + value: ShaderEffectUniform['value']; + programValue: number | Float32Array | undefined; + hasValidator: boolean; + hasProgramValueUpdater: boolean; + updateOnBind: boolean; + validatedValue?: number | number[]; +} + export interface ShaderEffectUniforms { [key: string]: ShaderEffectUniform; } diff --git a/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts b/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts index 3a014b12..237540c1 100644 --- a/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts +++ b/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts @@ -25,7 +25,6 @@ import { ImageTexture } from '../../../textures/ImageTexture.js'; import { TrFontFace, type NormalizedFontMetrics, - type TrFontFaceDescriptors, type TrFontFaceOptions, } from '../TrFontFace.js'; import type { FontShaper } from './internal/FontShaper.js'; @@ -77,25 +76,25 @@ export class SdfTrFontFace< 'SDF Font Faces can only be used with the WebGL Renderer', ); - this.texture = stage.txManager.loadTexture( - 'ImageTexture', - { - src: atlasUrl, - // IMPORTANT: The SDF shader requires the alpha channel to NOT be - // premultiplied on the atlas texture. If it is premultiplied, the - // rendering of SDF glyphs (especially single-channel SDF fonts) will - // be very jagged. - premultiplyAlpha: false, - }, - { - preload: true, - }, - ); + // Load image + this.texture = stage.txManager.loadTexture('ImageTexture', { + src: atlasUrl, + // IMPORTANT: The SDF shader requires the alpha channel to NOT be + // premultiplied on the atlas texture. If it is premultiplied, the + // rendering of SDF glyphs (especially single-channel SDF fonts) will + // be very jagged. + premultiplyAlpha: false, + }); this.texture.on('loaded', () => { this.checkLoaded(); + // Make sure we mark the stage for a re-render (in case the font's texture was freed and reloaded) + stage.requestRender(); }); + // Pre-load it + this.texture.ctxTexture.load(); + // Set this.data to the fetched data from dataUrl fetch(atlasDataUrl) .then(async (response) => { diff --git a/src/core/text-rendering/font-face-types/TrFontFace.ts b/src/core/text-rendering/font-face-types/TrFontFace.ts index d22fb072..307b0362 100644 --- a/src/core/text-rendering/font-face-types/TrFontFace.ts +++ b/src/core/text-rendering/font-face-types/TrFontFace.ts @@ -61,7 +61,6 @@ export interface TrFontFaceDescriptors { unicodeRange?: string; display?: FontDisplay; featureSettings?: string; - variant?: string; } export interface FontMetrics { @@ -182,7 +181,6 @@ export class TrFontFace extends EventEmitter { : descriptors.weight, stretch: descriptors.stretch, unicodeRange: descriptors.unicodeRange, - variant: descriptors.variant, featureSettings: descriptors.featureSettings, display: descriptors.display, }; diff --git a/src/core/text-rendering/font-face-types/WebTrFontFace.ts b/src/core/text-rendering/font-face-types/WebTrFontFace.ts index a82d5ba8..ab745eaf 100644 --- a/src/core/text-rendering/font-face-types/WebTrFontFace.ts +++ b/src/core/text-rendering/font-face-types/WebTrFontFace.ts @@ -57,7 +57,6 @@ export class WebTrFontFace extends TrFontFace { : determinedDescriptors.weight, stretch: determinedDescriptors.stretch, unicodeRange: determinedDescriptors.unicodeRange, - variant: determinedDescriptors.variant, featureSettings: determinedDescriptors.featureSettings, display: determinedDescriptors.display, }; diff --git a/src/core/text-rendering/renderers/CanvasTextRenderer.ts b/src/core/text-rendering/renderers/CanvasTextRenderer.ts index dbe99311..b6c15979 100644 --- a/src/core/text-rendering/renderers/CanvasTextRenderer.ts +++ b/src/core/text-rendering/renderers/CanvasTextRenderer.ts @@ -18,19 +18,13 @@ */ import { EventEmitter } from '../../../common/EventEmitter.js'; -import { assertTruthy, mergeColorAlphaPremultiplied } from '../../../utils.js'; +import { assertTruthy } from '../../../utils.js'; +import type { CoreNode } from '../../CoreNode.js'; +import type { CoreTextNode } from '../../CoreTextNode.js'; import type { Stage } from '../../Stage.js'; -import type { Matrix3d } from '../../lib/Matrix3d.js'; import { - intersectRect, - type Bound, - intersectBound, getNormalizedRgbaComponents, - type Rect, getNormalizedAlphaComponent, - type BoundWithValid, - createBound, - type RectWithValid, } from '../../lib/utils.js'; import type { ImageTexture } from '../../textures/ImageTexture.js'; import { TrFontManager, type FontFamilyMap } from '../TrFontManager.js'; @@ -62,13 +56,6 @@ declare module './TextRenderer.js' { } } -interface CanvasPageInfo { - texture: ImageTexture | undefined; - lineNumStart: number; - lineNumEnd: number; - valid: boolean; -} - function getFontCssString(props: TrProps): string { const { fontFamily, fontStyle, fontWeight, fontStretch, fontSize } = props; return [fontStyle, fontWeight, fontStretch, `${fontSize}px`, fontFamily].join( @@ -77,9 +64,8 @@ function getFontCssString(props: TrProps): string { } export interface CanvasTextRendererState extends TextRendererState { + node: CoreTextNode; props: TrProps; - - fontFaceLoadedHandler: (() => void) | undefined; fontInfo: | { fontFace: WebTrFontFace; @@ -87,28 +73,16 @@ export interface CanvasTextRendererState extends TextRendererState { loaded: boolean; } | undefined; - canvasPages: [CanvasPageInfo, CanvasPageInfo, CanvasPageInfo] | undefined; + textureNode: CoreNode | undefined; lightning2TextRenderer: LightningTextTextureRenderer; renderInfo: RenderInfo | undefined; - renderWindow: Bound | undefined; - visibleWindow: BoundWithValid; } -/** - * Ephemeral bounds object used for intersection calculations - * - * @remarks - * Used to avoid creating a new object every time we need to intersect - * element bounds. - */ -const tmpElementBounds = createBound(0, 0, 0, 0); - export class CanvasTextRenderer extends TextRenderer { protected canvas: OffscreenCanvas | HTMLCanvasElement; protected context: | OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D; - private rendererBounds: Bound; /** * Font family map used to store web font faces that were added to the * canvas text renderer. @@ -124,25 +98,21 @@ export class CanvasTextRenderer extends TextRenderer { this.canvas = document.createElement('canvas'); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - let context = this.canvas.getContext('2d') as - | OffscreenCanvasRenderingContext2D - | CanvasRenderingContext2D - | null; + let context = this.canvas.getContext('2d', { + willReadFrequently: true, + }) as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null; if (!context) { // A browser may appear to support OffscreenCanvas but not actually support the Canvas '2d' context // Here we try getting the context again after falling back to an HTMLCanvasElement. // See: https://github.com/lightning-js/renderer/issues/26#issuecomment-1750438486 this.canvas = document.createElement('canvas'); - context = this.canvas.getContext('2d'); + context = this.canvas.getContext('2d', { + willReadFrequently: true, + }); } assertTruthy(context); this.context = context; - this.rendererBounds = { - x1: 0, - y1: 0, - x2: this.stage.options.appWidth, - y2: this.stage.options.appHeight, - }; + // Install the default 'san-serif' font face this.addFontFace( new WebTrFontFace({ @@ -197,11 +167,9 @@ export class CanvasTextRenderer extends TextRenderer { }, x: (state, value) => { state.props.x = value; - this.invalidateVisibleWindowCache(state); }, y: (state, value) => { state.props.y = value; - this.invalidateVisibleWindowCache(state); }, contain: (state, value) => { state.props.contain = value; @@ -252,9 +220,6 @@ export class CanvasTextRenderer extends TextRenderer { state.props.overflowSuffix = value; this.invalidateLayoutCache(state); }, - // debug: (state, value) => { - // state.props.debug = value; - // }, }; } @@ -296,31 +261,26 @@ export class CanvasTextRenderer extends TextRenderer { faceSet.add(fontFace); } - override createState(props: TrProps): CanvasTextRendererState { + override createState( + props: TrProps, + node: CoreTextNode, + ): CanvasTextRendererState { return { + node, props, status: 'initialState', updateScheduled: false, emitter: new EventEmitter(), - canvasPages: undefined, + textureNode: undefined, lightning2TextRenderer: new LightningTextTextureRenderer( this.canvas, this.context, ), - renderWindow: undefined, - visibleWindow: { - x1: 0, - y1: 0, - x2: 0, - y2: 0, - valid: false, - }, renderInfo: undefined, forceFullLayoutCalc: false, textW: 0, textH: 0, fontInfo: undefined, - fontFaceLoadedHandler: undefined, isRenderable: false, debugData: { updateCount: 0, @@ -338,30 +298,20 @@ export class CanvasTextRenderer extends TextRenderer { // On the first update call we need to set the status to loading if (state.status === 'initialState') { this.setStatus(state, 'loading'); + // check if we're on screen + // if (this.isValidOnScreen(state) === true) { + // this.setStatus(state, 'loading'); + // } + } + + if (state.status === 'loaded') { + // If we're loaded, we don't need to do anything + return; } // If fontInfo is invalid, we need to establish it if (!state.fontInfo) { - const cssString = getFontCssString(state.props); - const trFontFace = TrFontManager.resolveFontFace( - this.fontFamilyArray, - state.props, - ) as WebTrFontFace | undefined; - assertTruthy(trFontFace, `Could not resolve font face for ${cssString}`); - state.fontInfo = { - fontFace: trFontFace, - cssString: cssString, - // TODO: For efficiency we would use this here but it's not reliable on WPE -> document.fonts.check(cssString), - loaded: false, - }; - // If font is not loaded, set up a handler to update the font info when the font loads - if (!state.fontInfo.loaded) { - globalFontSet - .load(cssString) - .then(this.onFontLoaded.bind(this, state, cssString)) - .catch(this.onFontLoadError.bind(this, state, cssString)); - return; - } + return this.loadFont(state); } // If we're waiting for a font face to load, don't render anything @@ -370,402 +320,145 @@ export class CanvasTextRenderer extends TextRenderer { } if (!state.renderInfo) { - state.lightning2TextRenderer.settings = { - text: state.props.text, - textAlign: state.props.textAlign, - fontFamily: state.props.fontFamily, - trFontFace: state.fontInfo.fontFace, - fontSize: state.props.fontSize, - fontStyle: [ - state.props.fontStretch, - state.props.fontStyle, - state.props.fontWeight, - ].join(' '), - textColor: getNormalizedRgbaComponents(state.props.color), - offsetY: state.props.offsetY, - wordWrap: state.props.contain !== 'none', - wordWrapWidth: - state.props.contain === 'none' ? undefined : state.props.width, - letterSpacing: state.props.letterSpacing, - lineHeight: state.props.lineHeight ?? null, - maxLines: state.props.maxLines, - maxHeight: - state.props.contain === 'both' - ? state.props.height - state.props.offsetY - : null, - textBaseline: state.props.textBaseline, - verticalAlign: state.props.verticalAlign, - overflowSuffix: state.props.overflowSuffix, - w: state.props.contain !== 'none' ? state.props.width : undefined, - }; - // const renderInfoCalculateTime = performance.now(); - state.renderInfo = state.lightning2TextRenderer.calculateRenderInfo(); - // console.log( - // 'Render info calculated in', - // performance.now() - renderInfoCalculateTime, - // 'ms', - // ); + state.renderInfo = this.calculateRenderInfo(state); state.textH = state.renderInfo.lineHeight * state.renderInfo.lines.length; state.textW = state.renderInfo.width; - - // Invalidate renderWindow because the renderInfo changed - state.renderWindow = undefined; + this.renderSingleCanvasPage(state); } - const { x, y, width, height, scrollY, contain } = state.props; - const { visibleWindow } = state; - let { renderWindow, canvasPages } = state; - - if (!visibleWindow.valid) { - // Figure out whats actually in the bounds of the renderer/canvas (visibleWindow) - const elementBounds = createBound( - x, - y, - contain !== 'none' ? x + width : Infinity, - contain === 'both' ? y + height : Infinity, - tmpElementBounds, - ); - /** - * Area that is visible on the screen. - */ - intersectBound(this.rendererBounds, elementBounds, visibleWindow); - visibleWindow.valid = true; - } + // handle scrollable text !!! + // if (state.isScrollable === true) { + // return this.renderScrollableCanvasPages(state); + // } - const visibleWindowHeight = visibleWindow.y2 - visibleWindow.y1; + // handle single page text + } - const maxLinesPerCanvasPage = Math.ceil( - visibleWindowHeight / state.renderInfo.lineHeight, - ); + renderSingleCanvasPage(state: CanvasTextRendererState): void { + assertTruthy(state.renderInfo); + const node = state.node; - if (visibleWindowHeight === 0) { - // Nothing to render. Clear any canvasPages and existing renderWindow - // Return early. - canvasPages = undefined; - renderWindow = undefined; - this.setStatus(state, 'loaded'); - return; - } else if (renderWindow && canvasPages) { - // Return early if we're still viewing inside the established render window - // No need to re-render what we've already rendered - const renderWindowScreenX1 = x + renderWindow.x1; - const renderWindowScreenY1 = y - scrollY + renderWindow.y1; - const renderWindowScreenX2 = x + renderWindow.x2; - const renderWindowScreenY2 = y - scrollY + renderWindow.y2; - - if ( - renderWindowScreenX1 <= visibleWindow.x1 && - renderWindowScreenX2 >= visibleWindow.x2 && - renderWindowScreenY1 <= visibleWindow.y1 && - renderWindowScreenY2 >= visibleWindow.y2 + const texture = this.stage.txManager.loadTexture('ImageTexture', { + src: function ( + this: CanvasTextRenderer, + lightning2TextRenderer: LightningTextTextureRenderer, + renderInfo: RenderInfo, ) { - this.setStatus(state, 'loaded'); - return; - } - if (renderWindowScreenY2 < visibleWindow.y2) { - // We've scrolled up, so we need to render the next page - renderWindow.y1 += maxLinesPerCanvasPage * state.renderInfo.lineHeight; - renderWindow.y2 += maxLinesPerCanvasPage * state.renderInfo.lineHeight; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - canvasPages.push(canvasPages.shift()!); - canvasPages[2].lineNumStart = - canvasPages[1].lineNumStart + maxLinesPerCanvasPage; - canvasPages[2].lineNumEnd = - canvasPages[2].lineNumStart + maxLinesPerCanvasPage; - canvasPages[2].valid = false; - } else if (renderWindowScreenY1 > visibleWindow.y1) { - // We've scrolled down, so we need to render the previous page - renderWindow.y1 -= maxLinesPerCanvasPage * state.renderInfo.lineHeight; - renderWindow.y2 -= maxLinesPerCanvasPage * state.renderInfo.lineHeight; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - canvasPages.unshift(canvasPages.pop()!); - canvasPages[0].lineNumStart = - canvasPages[1].lineNumStart - maxLinesPerCanvasPage; - canvasPages[0].lineNumEnd = - canvasPages[0].lineNumStart + maxLinesPerCanvasPage; - canvasPages[0].valid = false; - } - } else { - const pageHeight = state.renderInfo.lineHeight * maxLinesPerCanvasPage; - const page1Block = Math.ceil(scrollY / pageHeight); - const page1LineStart = page1Block * maxLinesPerCanvasPage; - const page0LineStart = page1LineStart - maxLinesPerCanvasPage; - const page2LineStart = page1LineStart + maxLinesPerCanvasPage; - - // We haven't rendered anything yet, so we need to render the first page - // If canvasPages already exist, let's re-use the textures - canvasPages = [ - { - texture: canvasPages?.[0].texture, - lineNumStart: page0LineStart, - lineNumEnd: page0LineStart + maxLinesPerCanvasPage, - valid: false, - }, - { - texture: canvasPages?.[1].texture, - lineNumStart: page1LineStart, - lineNumEnd: page1LineStart + maxLinesPerCanvasPage, - valid: false, - }, - { - texture: canvasPages?.[2].texture, - lineNumStart: page2LineStart, - lineNumEnd: page2LineStart + maxLinesPerCanvasPage, - valid: false, - }, - ]; - state.canvasPages = canvasPages; - - const scrollYNearestPage = page1Block * pageHeight; - - renderWindow = { - x1: 0, - y1: scrollYNearestPage - pageHeight, - x2: width, - y2: scrollYNearestPage + pageHeight * 2, - }; - } - - state.renderWindow = renderWindow; - - const pageDrawTime = performance.now(); - for (const pageInfo of canvasPages) { - if (pageInfo.valid) continue; - if (pageInfo.lineNumStart < 0) { - pageInfo.texture?.setRenderableOwner(state, false); - pageInfo.texture = this.stage.txManager.loadTexture('ImageTexture', { - src: '', + // load the canvas texture + assertTruthy(renderInfo); + lightning2TextRenderer.draw(renderInfo, { + lines: renderInfo.lines, + lineWidths: renderInfo.lineWidths, }); - pageInfo.texture.setRenderableOwner(state, state.isRenderable); - pageInfo.valid = true; - continue; - } - state.lightning2TextRenderer.draw(state.renderInfo, { - lines: state.renderInfo.lines.slice( - pageInfo.lineNumStart, - pageInfo.lineNumEnd, - ), - lineWidths: state.renderInfo.lineWidths.slice( - pageInfo.lineNumStart, - pageInfo.lineNumEnd, - ), - }); - if (!(this.canvas.width === 0 || this.canvas.height === 0)) { - pageInfo.texture?.setRenderableOwner(state, false); - pageInfo.texture = this.stage.txManager.loadTexture( - 'ImageTexture', - { - src: this.context.getImageData( - 0, - 0, - this.canvas.width, - this.canvas.height, - ), - }, - { - preload: true, - }, + if (this.canvas.width === 0 || this.canvas.height === 0) { + return null; + } + return this.context.getImageData( + 0, + 0, + this.canvas.width, + this.canvas.height, ); - pageInfo.texture.setRenderableOwner(state, state.isRenderable); - } - pageInfo.valid = true; + }.bind(this, state.lightning2TextRenderer, state.renderInfo), + }); + if (state.textureNode) { + // Use the existing texture node + state.textureNode.texture = texture; + } else { + // Create a new texture node + const textureNode = this.stage.createNode({ + parent: node, + texture, + autosize: true, + // The alpha channel of the color is ignored when rasterizing the text + // texture so we need to pass it directly to the texture node. + alpha: getNormalizedAlphaComponent(state.props.color), + }); + state.textureNode = textureNode; } - // console.log('pageDrawTime', performance.now() - pageDrawTime, 'ms'); - // Report final status this.setStatus(state, 'loaded'); } - override renderQuads( - state: CanvasTextRendererState, - transform: Matrix3d, - clippingRect: RectWithValid, - alpha: number, - ): void { - const { stage } = this; - - const { canvasPages, textW = 0, textH = 0, renderWindow } = state; - - if (!canvasPages || !renderWindow) return; - - const { x, y, scrollY, contain, width, height /*, debug*/ } = state.props; - - const elementRect = { - x: x, - y: y, - width: contain !== 'none' ? width : textW, - height: contain === 'both' ? height : textH, + loadFont = (state: CanvasTextRendererState): void => { + const cssString = getFontCssString(state.props); + const trFontFace = TrFontManager.resolveFontFace( + this.fontFamilyArray, + state.props, + ) as WebTrFontFace | undefined; + assertTruthy(trFontFace, `Could not resolve font face for ${cssString}`); + state.fontInfo = { + fontFace: trFontFace, + cssString: cssString, + // TODO: For efficiency we would use this here but it's not reliable on WPE -> document.fonts.check(cssString), + loaded: false, }; - - const visibleRect = intersectRect( - { - x: 0, - y: 0, - width: stage.options.appWidth, - height: stage.options.appHeight, - }, - elementRect, - ); - - // if (!debug.disableScissor) { - // renderer.enableScissor( - // visibleRect.x, - // visibleRect.y, - // visibleRect.w, - // visibleRect.h, - // ); - // } - - assertTruthy(canvasPages, 'canvasPages is not defined'); - assertTruthy(renderWindow, 'renderWindow is not defined'); - - const renderWindowHeight = renderWindow.y2 - renderWindow.y1; - const pageSize = renderWindowHeight / 3.0; - - const { zIndex, color } = state.props; - - // Color alpha of text is not properly rendered to the Canvas texture, so we - // need to apply it here. - const combinedAlpha = alpha * getNormalizedAlphaComponent(color); - const quadColor = mergeColorAlphaPremultiplied(0xffffffff, combinedAlpha); - if (canvasPages[0].valid) { - this.stage.renderer.addQuad({ - alpha: combinedAlpha, - clippingRect, - colorBl: quadColor, - colorBr: quadColor, - colorTl: quadColor, - colorTr: quadColor, - width: canvasPages[0].texture?.dimensions?.width || 0, - height: canvasPages[0].texture?.dimensions?.height || 0, - texture: canvasPages[0].texture!, - textureOptions: {}, - shader: null, - shaderProps: null, - zIndex, - tx: transform.tx, - ty: transform.ty - scrollY + renderWindow.y1, - ta: transform.ta, - tb: transform.tb, - tc: transform.tc, - td: transform.td, - }); - } - if (canvasPages[1].valid) { - this.stage.renderer.addQuad({ - alpha: combinedAlpha, - clippingRect, - colorBl: quadColor, - colorBr: quadColor, - colorTl: quadColor, - colorTr: quadColor, - width: canvasPages[1].texture?.dimensions?.width || 0, - height: canvasPages[1].texture?.dimensions?.height || 0, - texture: canvasPages[1].texture!, - textureOptions: {}, - shader: null, - shaderProps: null, - zIndex, - tx: transform.tx, - ty: transform.ty - scrollY + renderWindow.y1 + pageSize, - ta: transform.ta, - tb: transform.tb, - tc: transform.tc, - td: transform.td, - }); - } - if (canvasPages[2].valid) { - this.stage.renderer.addQuad({ - alpha: combinedAlpha, - clippingRect, - colorBl: quadColor, - colorBr: quadColor, - colorTl: quadColor, - colorTr: quadColor, - width: canvasPages[2].texture?.dimensions?.width || 0, - height: canvasPages[2].texture?.dimensions?.height || 0, - texture: canvasPages[2].texture!, - textureOptions: {}, - shader: null, - shaderProps: null, - zIndex, - tx: transform.tx, - ty: transform.ty - scrollY + renderWindow.y1 + pageSize + pageSize, - ta: transform.ta, - tb: transform.tb, - tc: transform.tc, - td: transform.td, - }); + // If font is not loaded, set up a handler to update the font info when the font loads + if (!state.fontInfo.loaded) { + globalFontSet + .load(cssString) + .then(this.onFontLoaded.bind(this, state, cssString)) + .catch(this.onFontLoadError.bind(this, state, cssString)); + return; } - - // renderer.disableScissor(); - - // if (debug.showElementRect) { - // this.renderer.drawBorder( - // Colors.Blue, - // elementRect.x, - // elementRect.y, - // elementRect.w, - // elementRect.h, - // ); - // } - - // if (debug.showVisibleRect) { - // this.renderer.drawBorder( - // Colors.Green, - // visibleRect.x, - // visibleRect.y, - // visibleRect.w, - // visibleRect.h, - // ); - // } - - // if (debug.showRenderWindow && renderWindow) { - // this.renderer.drawBorder( - // Colors.Red, - // x + renderWindow.x1, - // y + renderWindow.y1 - scrollY, - // x + renderWindow.x2 - (x + renderWindow.x1), - // y + renderWindow.y2 - scrollY - (y + renderWindow.y1 - scrollY), - // ); - // } + }; + + calculateRenderInfo(state: CanvasTextRendererState): RenderInfo { + state.lightning2TextRenderer.settings = { + text: state.props.text, + textAlign: state.props.textAlign, + fontFamily: state.props.fontFamily, + trFontFace: state.fontInfo?.fontFace, + fontSize: state.props.fontSize, + fontStyle: [ + state.props.fontStretch, + state.props.fontStyle, + state.props.fontWeight, + ].join(' '), + textColor: getNormalizedRgbaComponents(state.props.color), + offsetY: state.props.offsetY, + wordWrap: state.props.contain !== 'none', + wordWrapWidth: + state.props.contain === 'none' ? undefined : state.props.width, + letterSpacing: state.props.letterSpacing, + lineHeight: state.props.lineHeight ?? null, + maxLines: state.props.maxLines, + maxHeight: + state.props.contain === 'both' + ? state.props.height - state.props.offsetY + : null, + textBaseline: state.props.textBaseline, + verticalAlign: state.props.verticalAlign, + overflowSuffix: state.props.overflowSuffix, + w: state.props.contain !== 'none' ? state.props.width : undefined, + }; + state.renderInfo = state.lightning2TextRenderer.calculateRenderInfo(); + return state.renderInfo; } - override setIsRenderable( - state: CanvasTextRendererState, - renderable: boolean, - ): void { - super.setIsRenderable(state, renderable); - // Set state object owner from any canvas page textures - state.canvasPages?.forEach((pageInfo) => { - pageInfo.texture?.setRenderableOwner(state, renderable); - }); + override renderQuads(): void { + // Do nothing. The renderer will render the child node(s) that were created + // in the state update. + return; } override destroyState(state: CanvasTextRendererState): void { + if (state.status === 'destroyed') { + return; + } super.destroyState(state); - // Remove state object owner from any canvas page textures - state.canvasPages?.forEach((pageInfo) => { - pageInfo.texture?.setRenderableOwner(state, false); - }); - } - //#endregion Overrides - /** - * Invalidate the visible window stored in the state. This will cause a new - * visible window to be calculated on the next update. - * - * @param state - */ - protected invalidateVisibleWindowCache(state: CanvasTextRendererState): void { - state.visibleWindow.valid = false; - this.setStatus(state, 'loading'); - this.scheduleUpdateState(state); + if (state.textureNode) { + state.textureNode.destroy(); + delete state.textureNode; + } + delete state.renderInfo; } + //#endregion Overrides /** * Invalidate the layout cache stored in the state. This will cause the text - * to be re-layed out on the next update. + * to be re-rendered on the next update. * * @remarks * This also invalidates the visible window cache. @@ -774,7 +467,6 @@ export class CanvasTextRenderer extends TextRenderer { */ private invalidateLayoutCache(state: CanvasTextRendererState): void { state.renderInfo = undefined; - state.visibleWindow.valid = false; this.setStatus(state, 'loading'); this.scheduleUpdateState(state); } diff --git a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts index 0e0c247c..27c01d82 100644 --- a/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +++ b/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts @@ -166,7 +166,6 @@ export class LightningTextTextureRenderer { | OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D; private _settings: Settings; - private renderInfo: RenderInfo | undefined; constructor( canvas: OffscreenCanvas | HTMLCanvasElement, @@ -619,8 +618,6 @@ export class LightningTextTextureRenderer { if (renderInfo.cutSx || renderInfo.cutSy) { this._context.translate(renderInfo.cutSx, renderInfo.cutSy); } - - this.renderInfo = renderInfo; } wrapWord(word: string, wordWrapWidth: number, suffix: string) { diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts b/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts index d08d447a..382279b6 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts @@ -403,7 +403,7 @@ export class SdfTextRenderer extends TextRenderer { this.setStatus(state, 'failed', new Error(msg)); return; } - trFontFace.texture.setRenderableOwner(state, state.isRenderable); + trFontFace.texture.setRenderableOwner(state, true); } // If the font hasn't been loaded yet, stop here. @@ -712,7 +712,7 @@ export class SdfTextRenderer extends TextRenderer { const texture = state.trFontFace?.texture; assertTruthy(texture); - const ctxTexture = this.stage.txManager.getCtxTexture(texture); + const ctxTexture = texture.ctxTexture; renderOp.addTexture(ctxTexture as WebGlCoreCtxTexture); renderOp.length = state.bufferNumFloats; diff --git a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts index 429810a9..a8e1cc06 100644 --- a/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +++ b/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts @@ -360,7 +360,8 @@ export function layoutText( const xOffset = (vertexTextW - lineWidth) / 2; for (let j = line.bufferStart; j < line.bufferEnd; j += 4) { - vertexBuffer[j] += xOffset; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + vertexBuffer[j]! += xOffset; } } } else if (textAlign === 'right') { @@ -376,7 +377,8 @@ export function layoutText( const xOffset = vertexTextW - lineWidth; for (let j = line.bufferStart; j < line.bufferEnd; j += 4) { - vertexBuffer[j] += xOffset; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + vertexBuffer[j]! += xOffset; } } } diff --git a/src/core/text-rendering/renderers/TextRenderer.ts b/src/core/text-rendering/renderers/TextRenderer.ts index 28cb4fe7..cc655a8f 100644 --- a/src/core/text-rendering/renderers/TextRenderer.ts +++ b/src/core/text-rendering/renderers/TextRenderer.ts @@ -19,6 +19,7 @@ import type { Dimensions } from '../../../common/CommonTypes.js'; import type { EventEmitter } from '../../../common/EventEmitter.js'; +import type { CoreTextNode } from '../../CoreTextNode.js'; import type { Stage } from '../../Stage.js'; import type { Matrix3d } from '../../lib/Matrix3d.js'; import type { Rect, RectWithValid } from '../../lib/utils.js'; @@ -501,7 +502,7 @@ export abstract class TextRenderer< */ abstract addFontFace(fontFace: TrFontFace): void; - abstract createState(props: TrProps): StateT; + abstract createState(props: TrProps, node: CoreTextNode): StateT; /** * Destroy/Clean up the state object @@ -544,7 +545,7 @@ export abstract class TextRenderer< abstract updateState(state: StateT): void; - abstract renderQuads( + renderQuads?( state: StateT, transform: Matrix3d, clippingRect: RectWithValid, diff --git a/src/core/textures/ColorTexture.ts b/src/core/textures/ColorTexture.ts index b9754c5b..ec561c9b 100644 --- a/src/core/textures/ColorTexture.ts +++ b/src/core/textures/ColorTexture.ts @@ -39,7 +39,7 @@ export interface ColorTextureProps { * The pixel color is set with the {@link ColorTextureProps.color} prop. * * This is the default texture used for a Node if it's - * {@link INodeWritableProps.texture} prop is set to `null` (the default) + * {@link INodeProps.texture} prop is set to `null` (the default) * * Generally the 1x1 color pixel is stretched to whatever the set dimensions of * a Node are. diff --git a/src/core/textures/ImageTexture.ts b/src/core/textures/ImageTexture.ts index 0ecc9b5c..b521a3f9 100644 --- a/src/core/textures/ImageTexture.ts +++ b/src/core/textures/ImageTexture.ts @@ -38,7 +38,7 @@ export interface ImageTextureProps { * * @default '' */ - src?: string | ImageData | (() => ImageData); + src?: string | ImageData | (() => ImageData | null); /** * Whether to premultiply the alpha channel into the color channels of the * image. diff --git a/src/core/textures/NoiseTexture.ts b/src/core/textures/NoiseTexture.ts index a634c71d..1b89c982 100644 --- a/src/core/textures/NoiseTexture.ts +++ b/src/core/textures/NoiseTexture.ts @@ -77,7 +77,10 @@ export class NoiseTexture extends Texture { }; } - static override makeCacheKey(props: NoiseTextureProps): string { + static override makeCacheKey(props: NoiseTextureProps): string | false { + if (props.cacheId === undefined) { + return false; + } const resolvedProps = NoiseTexture.resolveDefaults(props); return `NoiseTexture,${resolvedProps.width},${resolvedProps.height},${resolvedProps.cacheId}`; } diff --git a/src/core/textures/SubTexture.ts b/src/core/textures/SubTexture.ts index 4fc322d0..e18a1ae6 100644 --- a/src/core/textures/SubTexture.ts +++ b/src/core/textures/SubTexture.ts @@ -17,7 +17,6 @@ * limitations under the License. */ -import type { TextureRef } from '../../main-api/RendererMain.js'; import type { CoreTextureManager } from '../CoreTextureManager.js'; import { Texture, @@ -33,7 +32,7 @@ export interface SubTextureProps { /** * The texture that this sub-texture is a sub-region of. */ - texture: TextureRef; + texture: Texture; /** * The x pixel position of the top-left of the sub-texture within the parent @@ -80,12 +79,8 @@ export class SubTexture extends Texture { constructor(txManager: CoreTextureManager, props: SubTextureProps) { super(txManager); - this.parentTexture = this.txManager.loadTexture( - props.texture.txType, - props.texture.props, - props.texture.options, - ); this.props = SubTexture.resolveDefaults(props || {}); + this.parentTexture = this.props.texture; // If parent texture is already loaded / failed, trigger loaded event manually // so that users get a consistent event experience. @@ -116,6 +111,11 @@ export class SubTexture extends Texture { this.setState('failed', error); }; + override onChangeIsRenderable(isRenderable: boolean): void { + // Propagate the renderable owner change to the parent texture + this.parentTexture.setRenderableOwner(this, isRenderable); + } + override async getTextureData(): Promise { return { data: this.props, diff --git a/src/core/textures/Texture.ts b/src/core/textures/Texture.ts index d7fce11f..0cfe4bde 100644 --- a/src/core/textures/Texture.ts +++ b/src/core/textures/Texture.ts @@ -21,6 +21,7 @@ import type { CoreTextureManager } from '../CoreTextureManager.js'; import type { SubTextureProps } from './SubTexture.js'; import type { Dimensions } from '../../common/CommonTypes.js'; import { EventEmitter } from '../../common/EventEmitter.js'; +import type { CoreContextTexture } from '../renderers/CoreContextTexture.js'; /** * Event handler for when a Texture is freed @@ -145,6 +146,10 @@ export abstract class Texture extends EventEmitter { readonly renderableOwners = new Set(); + readonly renderable: boolean = false; + + readonly lastRenderableChangeTime = 0; + constructor(protected txManager: CoreTextureManager) { super(); } @@ -164,18 +169,56 @@ export abstract class Texture extends EventEmitter { * @param renderable */ setRenderableOwner(owner: unknown, renderable: boolean): void { + const oldSize = this.renderableOwners.size; if (renderable) { this.renderableOwners.add(owner); + const newSize = this.renderableOwners.size; + if (newSize > oldSize && newSize === 1) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + (this.renderable as boolean) = true; + (this.lastRenderableChangeTime as number) = this.txManager.frameTime; + this.onChangeIsRenderable?.(true); + } } else { this.renderableOwners.delete(owner); + const newSize = this.renderableOwners.size; + if (newSize < oldSize && newSize === 0) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + (this.renderable as boolean) = false; + (this.lastRenderableChangeTime as number) = this.txManager.frameTime; + this.onChangeIsRenderable?.(false); + } } } /** - * Returns true if the texture is assigned to any Nodes that are renderable. + * Event called when the Texture becomes renderable or unrenderable. + * + * @remarks + * Used by subclasses like SubTexture propogate then renderability of the + * Texture to other referenced Textures. + * + * @param isRenderable `true` if this Texture has renderable owners. + */ + onChangeIsRenderable?(isRenderable: boolean): void; + + /** + * Get the CoreContextTexture for this Texture + * + * @remarks + * Each Texture has a corresponding CoreContextTexture that is used to + * manage the texture's native data depending on the renderer's mode + * (WebGL, Canvas, etc). + * + * The Texture and CoreContextTexture are always linked together in a 1:1 + * relationship. */ - get renderable(): boolean { - return this.renderableOwners.size > 0; + get ctxTexture() { + // The first time this is called, create the ctxTexture + const ctxTexture = this.txManager.renderer.createCtxTexture(this); + // And replace this getter with the value for future calls + Object.defineProperty(this, 'ctxTexture', { value: ctxTexture }); + return ctxTexture; } /** diff --git a/src/core/utils.ts b/src/core/utils.ts index bc1b4412..44865de5 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -197,10 +197,11 @@ export const getTimingFunction = ( }; } - if (timingLookup[str] !== undefined) { + const lookup = timingLookup[str]; + if (lookup !== undefined) { + const [a, b, c, d] = lookup; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - TS doesn't understand that we've checked for undefined - const [a, b, c, d] = timingLookup[str]; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const timing = getTimingBezier(a, b, c, d); timingMapping[str] = timing; @@ -215,12 +216,12 @@ export const getTimingFunction = ( return defaultTiming; }; -if (!Math.hypot) - Math.hypot = (...args: number[]) => { - let y = 0, - i = args.length; - while (i--) { - y += args[i]! * args[i]!; - } - return Math.sqrt(y); - }; +/** + * Convert bytes to string of megabytes with 2 decimal points + * + * @param bytes + * @returns + */ +export function bytesToMb(bytes: number) { + return (bytes / 1024 / 1024).toFixed(2); +} diff --git a/src/main-api/DynamicShaderController.ts b/src/main-api/DynamicShaderController.ts new file mode 100644 index 00000000..2e0686d0 --- /dev/null +++ b/src/main-api/DynamicShaderController.ts @@ -0,0 +1,104 @@ +import type { ShaderEffectValueMap } from '../../exports/index.js'; +import type { + CoreShaderManager, + EffectMap, + ShaderMap, +} from '../core/CoreShaderManager.js'; +import type { ExtractProps } from '../core/CoreTextureManager.js'; +import type { EffectDesc } from '../core/renderers/webgl/shaders/effects/ShaderEffect.js'; +import type { BaseShaderController } from './ShaderController.js'; + +type OptionalName = T extends string ? T : never; + +type MapEffectProps< + Effects extends [...{ name?: string; type: keyof EffectMap }[]], +> = { + [K in Effects[number] as OptionalName]: ExtractProps< + EffectMap[K['type']] + >; +}; + +export type DynamicEffects< + T extends [...{ name?: string; type: keyof EffectMap }[]], +> = { + [K in keyof T]: EffectDesc; +}; + +export class DynamicShaderController< + Effects extends [...{ name?: string; type: keyof EffectMap }[]], +> implements BaseShaderController +{ + private resolvedProps: ExtractProps; + props: MapEffectProps; + type: 'DynamicShader'; + + constructor( + readonly shader: InstanceType, + props: ExtractProps, + shManager: CoreShaderManager, + ) { + this.type = 'DynamicShader'; + this.resolvedProps = props; + const effectConstructors = shManager.getRegisteredEffects(); + const definedProps = {}; + + const effects = props.effects!; + const effectsLength = effects.length; + + for (let i = 0; i < effectsLength; i++) { + const { + name: effectName, + props: effectProps, + type: effectType, + } = effects[i]!; + if (effectName === undefined) { + continue; + } + const definedEffectProps = {}; + const propEntries = Object.keys(effectProps); + const propEntriesLength = propEntries.length; + for (let j = 0; j < propEntriesLength; j++) { + const propName = propEntries[j]!; + Object.defineProperty(definedEffectProps, propName, { + get: () => { + return ( + this.resolvedProps.effects![i]!.props[propName] as Record< + string, + any + > + ).value; + }, + set: (value) => { + const target = this.resolvedProps.effects![i]!.props[ + propName + ] as Record; + target.value = value; + if (target.hasValidator) { + value = target.validatedValue = effectConstructors[effectType]! + .uniforms[propName]?.validator!(value, effectProps); + } + if (target.hasProgramValueUpdater) { + effectConstructors[effectType]!.uniforms[propName] + ?.updateProgramValue!(target as ShaderEffectValueMap); + } else { + target.programValue = value; + } + shManager.renderer.stage.requestRender(); + }, + }); + } + + Object.defineProperty(definedProps, effectName, { + get: () => { + return definedEffectProps; + }, + }); + } + + this.props = definedProps as MapEffectProps; + } + + getResolvedProps() { + return this.resolvedProps; + } +} diff --git a/src/main-api/ICoreDriver.ts b/src/main-api/ICoreDriver.ts deleted file mode 100644 index 1b9183aa..00000000 --- a/src/main-api/ICoreDriver.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { - FpsUpdatePayload, - FrameTickPayload, -} from '../common/CommonTypes.js'; -import type { - INode, - INodeWritableProps, - ITextNode, - ITextNodeWritableProps, -} from './INode.js'; -import type { RendererMain, RendererMainSettings } from './RendererMain.js'; - -/** - * This interface is to be implemented by Core Drivers - * - * @remarks - * Both the {@link MainCoreDriver} and the {@link ThreadXCoreDriver} exist - * that implement this interface to support both the single-threaded and - * multi-threaded Core modes. - */ -export interface ICoreDriver { - init( - rendererMain: RendererMain, - rendererSettings: Required, - canvas: HTMLCanvasElement, - ): Promise; - - createNode(props: INodeWritableProps): INode; - - createTextNode(props: ITextNodeWritableProps): ITextNode; - - // TODO: Nodes can be destroyed from the INode directly. Do we need this method - // on this interface? All it does is call the destroy() method on the node. - destroyNode(node: INode): void; - - getRootNode(): INode; - - releaseTexture(textureDescId: number): void; - - onCreateNode(node: INode): void; - - onBeforeDestroyNode(node: INode): void; - - onFpsUpdate(fpsData: FpsUpdatePayload): void; - - onFrameTick(frameTickData: FrameTickPayload): void; - - onIdle?(): void; -} diff --git a/src/main-api/INode.ts b/src/main-api/INode.ts index 68f79e03..d2fa6a67 100644 --- a/src/main-api/INode.ts +++ b/src/main-api/INode.ts @@ -2,7 +2,7 @@ * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: * - * Copyright 2023 Comcast Cable Communications Management, LLC. + * Copyright 2024 Comcast Cable Communications Management, LLC. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. @@ -16,508 +16,86 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { IEventEmitter } from '@lightningjs/threadx'; import type { IAnimationController } from '../common/IAnimationController.js'; -import type { ShaderRef, TextureRef } from './RendererMain.js'; -import type { - TextRendererMap, - TrProps, -} from '../core/text-rendering/renderers/TextRenderer.js'; +import { + CoreNode, + type CoreNodeAnimateProps, + type CoreNodeProps, +} from '../core/CoreNode.js'; +import type { CoreTextNode, CoreTextNodeProps } from '../core/CoreTextNode.js'; import type { AnimationSettings } from '../core/animations/CoreAnimation.js'; +import type { BaseShaderController } from './ShaderController.js'; /** - * Writable properties of a Node. + * A visual Node in the Renderer scene graph. + * + * @remarks + * A Node is a basic building block of the Renderer scene graph. It can be a + * container for other Nodes, or it can be a leaf Node that renders a solid + * color, gradient, image, or specific texture, using a specific shader. + * + * For text rendering Nodes, see {@link ITextNode}. + * + * ## INode vs CoreNode + * CoreNode is the name of the class for a Renderer Node and is only directly + * used internally by the Renderer. INode describes the public API of a + * Renderer Node including the ability to be tied to a specific Shader. + * + * Users of the Renderer API, should generally interact with INode objects + * instead of CoreNode objects. */ -export interface INodeWritableProps { - /** - * The x coordinate of the Node's Mount Point. - * - * @remarks - * See {@link mountX} and {@link mountY} for more information about setting - * the Mount Point. - * - * @default `0` - */ - x: number; - /** - * The y coordinate of the Node's Mount Point. - * - * @remarks - * See {@link mountX} and {@link mountY} for more information about setting - * the Mount Point. - * - * @default `0` - */ - y: number; - /** - * The width of the Node. - * - * @default `0` - */ - width: number; - /** - * The height of the Node. - * - * @default `0` - */ - height: number; - /** - * The alpha opacity of the Node. - * - * @remarks - * The alpha value is a number between 0 and 1, where 0 is fully transparent - * and 1 is fully opaque. - * - * @default `1` - */ - alpha: number; - /** - * Autosize mode - * - * @remarks - * When enabled, when a texture is loaded into the Node, the Node will - * automatically resize to the dimensions of the texture. - * - * Text Nodes are always autosized based on their text content regardless - * of this mode setting. - * - * @default `false` - */ - autosize: boolean; - /** - * Clipping Mode - * - * @remarks - * Enable Clipping Mode when you want to prevent the drawing of a Node and - * its descendants from overflowing outside of the Node's x/y/width/height - * bounds. - * - * For WebGL, clipping is implemented using the high-performance WebGL - * operation scissor. As a consequence, clipping does not work for - * non-rectangular areas. So, if the element is rotated - * (by itself or by any of its ancestors), clipping will not work as intended. - * - * TODO: Add support for non-rectangular clipping either automatically or - * via Render-To-Texture. - * - * @default `false` - */ - clipping: boolean; - /** - * The color of the Node. - * - * @remarks - * The color value is a number in the format 0xRRGGBBAA, where RR is the red - * component, GG is the green component, BB is the blue component, and AA is - * the alpha component. - * - * Gradient colors may be set by setting the different color sub-properties: - * {@link colorTop}, {@link colorBottom}, {@link colorLeft}, {@link colorRight}, - * {@link colorTl}, {@link colorTr}, {@link colorBr}, {@link colorBl} accordingly. - * - * @default `0xffffffff` (opaque white) - */ - color: number; - /** - * The color of the top edge of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorTop: number; - /** - * The color of the bottom edge of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorBottom: number; - /** - * The color of the left edge of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorLeft: number; - /** - * The color of the right edge of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorRight: number; - /** - * The color of the top-left corner of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorTl: number; - /** - * The color of the top-right corner of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorTr: number; - /** - * The color of the bottom-right corner of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorBr: number; - /** - * The color of the bottom-left corner of the Node for gradient rendering. - * - * @remarks - * See {@link color} for more information about color values and gradient - * rendering. - */ - colorBl: number; - /** - * The Node's parent Node. - * - * @remarks - * The value `null` indicates that the Node has no parent. This may either be - * because the Node is the root Node of the scene graph, or because the Node - * has been removed from the scene graph. - * - * In order to make sure that a Node can be rendered on the screen, it must - * be added to the scene graph by setting it's parent property to a Node that - * is already in the scene graph such as the root Node. - * - * @default `null` - */ +export interface INode + extends Omit { + shader: SC; + animate( + props: Partial>, + settings: Partial, + ): IAnimationController; parent: INode | null; - /** - * The Node's z-index. - * - * @remarks - * TBD - */ - zIndex: number; - /** - * The Node's Texture. - * - * @remarks - * The `texture` defines a rasterized image that is contained within the - * {@link width} and {@link height} dimensions of the Node. If null, the - * Node will use an opaque white {@link ColorTexture} when being drawn, which - * essentially enables colors (including gradients) to be drawn. - * - * If set, by default, the texture will be drawn, as is, stretched to the - * dimensions of the Node. This behavior can be modified by setting the TBD - * and TBD properties. - * - * To create a Texture in order to set it on this property, call - * {@link RendererMain.createTexture}. - * - * If the {@link src} is set on a Node, the Node will use the - * {@link ImageTexture} by default and the Node will simply load the image at - * the specified URL. - * - * Note: If this is a Text Node, the Texture will be managed by the Node's - * {@link TextRenderer} and should not be set explicitly. - */ - texture: TextureRef | null; - /** - * The Node's shader - * - * @remarks - * The `shader` defines a {@link Shader} used to draw the Node. By default, - * the Default Shader is used which simply draws the defined {@link texture} - * or {@link color}(s) within the Node without any special effects. - * - * To create a Shader in order to set it on this property, call - * {@link RendererMain.createShader}. - * - * Note: If this is a Text Node, the Shader will be managed by the Node's - * {@link TextRenderer} and should not be set explicitly. - */ - shader: ShaderRef | null; - /** - * Image URL - * - * @remarks - * When set, the Node's {@link texture} is automatically set to an - * {@link ImageTexture} using the source image URL provided (with all other - * settings being defaults) - */ - src: string; - zIndexLocked: number; - /** - * Scale to render the Node at - * - * @remarks - * The scale value multiplies the provided {@link width} and {@link height} - * of the Node around the Node's Pivot Point (defined by the {@link pivot} - * props). - * - * Behind the scenes, setting this property sets both the {@link scaleX} and - * {@link scaleY} props to the same value. - * - * NOTE: When the scaleX and scaleY props are explicitly set to different values, - * this property returns `null`. Setting `null` on this property will have no - * effect. - * - * @default 1.0 - */ - scale: number | null; - /** - * Scale to render the Node at (X-Axis) - * - * @remarks - * The scaleX value multiplies the provided {@link width} of the Node around - * the Node's Pivot Point (defined by the {@link pivot} props). - * - * @default 1.0 - */ - scaleX: number; - /** - * Scale to render the Node at (Y-Axis) - * - * @remarks - * The scaleY value multiplies the provided {@link height} of the Node around - * the Node's Pivot Point (defined by the {@link pivot} props). - * - * @default 1.0 - */ - scaleY: number; - /** - * Combined position of the Node's Mount Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Mount Point at the top-left corner of the Node. - * - `0.5` defines it at the center of the Node. - * - `1.0` defines it at the bottom-right corner of the node. - * - * Use the {@link mountX} and {@link mountY} props seperately for more control - * of the Mount Point. - * - * When assigned, the same value is also passed to both the {@link mountX} and - * {@link mountY} props. - * - * @default 0 (top-left) - */ - mount: number; - /** - * X position of the Node's Mount Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Mount Point's X position as the left-most edge of the - * Node - * - `0.5` defines it as the horizontal center of the Node - * - `1.0` defines it as the right-most edge of the Node. - * - * The combination of {@link mountX} and {@link mountY} define the Mount Point - * - * @default 0 (left-most edge) - */ - mountX: number; - /** - * Y position of the Node's Mount Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Mount Point's Y position as the top-most edge of the - * Node - * - `0.5` defines it as the vertical center of the Node - * - `1.0` defines it as the bottom-most edge of the Node. - * - * The combination of {@link mountX} and {@link mountY} define the Mount Point - * - * @default 0 (top-most edge) - */ - mountY: number; - /** - * Combined position of the Node's Pivot Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Pivot Point at the top-left corner of the Node. - * - `0.5` defines it at the center of the Node. - * - `1.0` defines it at the bottom-right corner of the node. - * - * Use the {@link pivotX} and {@link pivotY} props seperately for more control - * of the Pivot Point. - * - * When assigned, the same value is also passed to both the {@link pivotX} and - * {@link pivotY} props. - * - * @default 0.5 (center) - */ - pivot: number; - /** - * X position of the Node's Pivot Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Pivot Point's X position as the left-most edge of the - * Node - * - `0.5` defines it as the horizontal center of the Node - * - `1.0` defines it as the right-most edge of the Node. - * - * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point - * - * @default 0.5 (centered on x-axis) - */ - pivotX: number; - /** - * Y position of the Node's Pivot Point - * - * @remarks - * The value can be any number between `0.0` and `1.0`: - * - `0.0` defines the Pivot Point's Y position as the top-most edge of the - * Node - * - `0.5` defines it as the vertical center of the Node - * - `1.0` defines it as the bottom-most edge of the Node. - * - * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point - * - * @default 0.5 (centered on y-axis) - */ - pivotY: number; - /** - * Rotation of the Node (in Radians) - * - * @remarks - * Sets the amount to rotate the Node by around it's Pivot Point (defined by - * the {@link pivot} props). Positive values rotate the Node clockwise, while - * negative values rotate it counter-clockwise. - * - * Example values: - * - `-Math.PI / 2`: 90 degree rotation counter-clockwise - * - `0`: No rotation - * - `Math.PI / 2`: 90 degree rotation clockwise - * - `Math.PI`: 180 degree rotation clockwise - * - `3 * Math.PI / 2`: 270 degree rotation clockwise - * - `2 * Math.PI`: 360 rotation clockwise - */ - rotation: number; - - /** - * Whether the Node is rendered to a texture - * - * @remarks - * TBD - * - * @default false - */ - rtt: boolean; - - /** - * Node data element for custom data storage (optional) - * - * @remarks - * This property is used to store custom data on the Node as a key/value data store. - * Data values are limited to string, numbers, booleans. Strings will be truncated - * to a 2048 character limit for performance reasons. - * - * This is not a data storage mechanism for large amounts of data please use a - * dedicated data storage mechanism for that. - * - * The custom data will be reflected in the inspector as part of `data-*` attributes - * - * @default `undefined` - */ - data?: CustomDataMap; } /** - * A custom data map which can be stored on the INode - * - * @remarks - * This is a map of key-value pairs that can be stored on an INode. It is used - * to store custom data that can be used by the application. - * The data stored can only be of type string, number or boolean. + * Properties used to animate() a Node */ -export type CustomDataMap = { - [key: string]: string | number | boolean | undefined; -}; - -export type INodeAnimatableProps = { - [Key in keyof INodeWritableProps as NonNullable< - INodeWritableProps[Key] - > extends number - ? Key - : never]: number; -}; +export interface INodeAnimateProps< + SC extends BaseShaderController = BaseShaderController, +> extends Omit { + shaderProps: Partial; +} -export interface INodeEvents { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [s: string]: (target: INode, data: any) => void; +/** + * Properties used to create a new Node + */ +export interface INodeProps< + SC extends BaseShaderController = BaseShaderController, +> extends Omit { + shader: SC; + parent: INode | null; } /** - * Main API interface representing a Node in the Renderer scene graph. + * A visual Node in the Renderer scene graph that renders text. * * @remarks - * A Node is a basic building block of the Renderer scene graph. It can be a - * container for other Nodes, or it can be a leaf Node that renders a solid - * color, gradient, image, or specific texture, using a specific shader. + * A Text Node is a special type of Node that renders text using a specific + * text renderer, such as Web/Canvas or Signed Distance Field (SDF) text. * - * For text rendering, see {@link ITextNode}. + * For non-text rendering, see {@link INode}. * - * Nodes are represented by an interface since they may be implemented in - * different ways depending on the Core Driver. For example, the MainCoreDriver - * implements it with it's `MainOnlyNode` while the ThreadXCoreDriver implements - * it with it's `ThreadXMainNode`. + * Users of the Renderer API, should generally interact with ITextNode objects + * instead of CoreTextNode objects. */ -export interface INode extends INodeWritableProps, IEventEmitter { - id: number; - readonly children: INode[]; +export interface ITextNode extends Omit { animate( - props: Partial, + props: Partial>, settings: Partial, ): IAnimationController; - destroy(): void; - flush(): void; -} - -export interface ITextNodeWritableProps extends INodeWritableProps, TrProps { - /** - * Force Text Node to use a specific Text Renderer - * - * @remarks - * By default, Text Nodes resolve the Text Renderer to use based on the font - * that is matched using the font family and other font selection properties. - * - * If two fonts supported by two separate Text Renderers are matched setting - * this override forces the Text Node to resolve to the Text Renderer defined - * here. - * - * @default null - */ - textRendererOverride: keyof TextRendererMap | null; + parent: INode | null; } /** - * Main API interface representing a Node in the Renderer scene graph that renders text. - * - * @remarks - * A Text Node is the second graphical building block of the Renderer scene - * graph. It renders text using a specific text renderer that is automatically - * chosen based on the font requested and what type of fonts are installed - * into an app via a CoreExtension. - * - * The text renderer can be overridden by setting the `textRendererOverride` - * - * The `texture` and `shader` properties are managed by loaded text renderer and - * should not be set directly. - * - * For non-text rendering, see {@link INode}. + * Properties used to create a new Text Node */ -export interface ITextNode extends INode, ITextNodeWritableProps { - // Based on inputs +export interface ITextNodeProps extends Omit { + parent: INode | null; } diff --git a/src/main-api/Inspector.ts b/src/main-api/Inspector.ts index d62227df..f5d6e4a3 100644 --- a/src/main-api/Inspector.ts +++ b/src/main-api/Inspector.ts @@ -1,15 +1,13 @@ -import type { - INode, - INodeAnimatableProps, - INodeWritableProps, - ITextNode, - ITextNodeWritableProps, -} from './INode.js'; -import type { ICoreDriver } from './ICoreDriver.js'; -import { type RendererMainSettings } from './RendererMain.js'; +import { + CoreNode, + type CoreNodeAnimateProps, + type CoreNodeProps, +} from '../core/CoreNode.js'; +import { type RendererMainSettings } from './Renderer.js'; import type { AnimationSettings } from '../core/animations/CoreAnimation.js'; import type { IAnimationController } from '../common/IAnimationController.js'; import { isProductionEnvironment } from '../utils.js'; +import type { CoreTextNode, CoreTextNodeProps } from '../core/CoreTextNode.js'; /** * Inspector @@ -219,31 +217,28 @@ export class Inspector { } createDiv( - node: INode | ITextNode, - properties: INodeWritableProps | ITextNodeWritableProps, + id: number, + properties: CoreNodeProps | CoreTextNodeProps, ): HTMLElement { const div = document.createElement('div'); div.style.position = 'absolute'; - div.id = node.id.toString(); + div.id = id.toString(); // set initial properties for (const key in properties) { this.updateNodeProperty( div, // really typescript? really? - key as keyof (INodeWritableProps & ITextNodeWritableProps), - (properties as INodeWritableProps & ITextNodeWritableProps)[ - key as keyof (INodeWritableProps & ITextNodeWritableProps) - ], + key as keyof CoreNodeProps, + properties[key as keyof CoreNodeProps], ); } return div; } - createNode(driver: ICoreDriver, properties: INodeWritableProps): INode { - const node = driver.createNode(properties); - const div = this.createDiv(node, properties); + createNode(node: CoreNode): CoreNode { + const div = this.createDiv(node.id, node.props); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any (div as any).node = node; @@ -254,40 +249,39 @@ export class Inspector { return this.createProxy(node, div); } - createTextNode( - driver: ICoreDriver, - properties: ITextNodeWritableProps, - ): ITextNode { - const node = driver.createTextNode(properties); - const div = this.createDiv(node, properties); + createTextNode(node: CoreNode): CoreTextNode { + const div = this.createDiv(node.id, node.props); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any (div as any).node = node; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any (node as any).div = div; - return this.createProxy(node, div) as ITextNode; + return this.createProxy(node, div) as CoreTextNode; } - createProxy(node: INode | ITextNode, div: HTMLElement): INode | ITextNode { + createProxy( + node: CoreNode | CoreTextNode, + div: HTMLElement, + ): CoreNode | CoreTextNode { return new Proxy(node, { - set: (target, property: keyof INodeWritableProps, value) => { + set: (target, property: keyof CoreNodeProps, value) => { this.updateNodeProperty(div, property, value); return Reflect.set(target, property, value); }, - get: (target, property: keyof INode, receiver: any): any => { + get: (target, property: keyof CoreNode, receiver: any): any => { if (property === 'destroy') { - this.destroyNode(target); + this.destroyNode(target.id); } if (property === 'animate') { - return (props: INodeAnimatableProps, settings: AnimationSettings) => { + return (props: CoreNodeAnimateProps, settings: AnimationSettings) => { const anim = target.animate(props, settings); // Trap the animate start function so we can update the inspector accordingly return new Proxy(anim, { get: (target, property: keyof IAnimationController, receiver) => { if (property === 'start') { - this.animateNode(div, node, props, settings); + this.animateNode(div, props, settings); } return Reflect.get(target, property, receiver); @@ -301,14 +295,14 @@ export class Inspector { }); } - destroyNode(node: INode | ITextNode) { - const div = document.getElementById(node.id.toString()); + destroyNode(id: number) { + const div = document.getElementById(id.toString()); div?.remove(); } updateNodeProperty( div: HTMLElement, - property: keyof INodeWritableProps | keyof ITextNodeWritableProps, + property: keyof CoreNodeProps | keyof CoreTextNodeProps, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, ) { @@ -320,7 +314,7 @@ export class Inspector { * Special case for parent property */ if (property === 'parent') { - const parentId: number = (value as INode).id; + const parentId: number = value.id; // only way to detect if the parent is the root node // if you are reading this and have a better way, please let me know @@ -385,7 +379,12 @@ export class Inspector { // DOM properties if (domPropertyMap[property]) { - div.setAttribute(String(stylePropertyMap[property]), String(value)); + const domProperty = domPropertyMap[property]; + if (!domProperty) { + return; + } + + div.setAttribute(String(domProperty), String(value)); return; } @@ -407,8 +406,7 @@ export class Inspector { // simple animation handler animateNode( div: HTMLElement, - node: INode, - props: INodeAnimatableProps, + props: CoreNodeAnimateProps, settings: AnimationSettings, ) { const { diff --git a/src/main-api/Renderer.ts b/src/main-api/Renderer.ts new file mode 100644 index 00000000..482985cd --- /dev/null +++ b/src/main-api/Renderer.ts @@ -0,0 +1,592 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { EffectMap, ShaderMap } from '../core/CoreShaderManager.js'; +import type { ExtractProps, TextureMap } from '../core/CoreTextureManager.js'; +import { EventEmitter } from '../common/EventEmitter.js'; +import { Inspector } from './Inspector.js'; +import { assertTruthy, isProductionEnvironment } from '../utils.js'; +import { Stage } from '../core/Stage.js'; +import { CoreNode, type CoreNodeProps } from '../core/CoreNode.js'; +import { type CoreTextNodeProps } from '../core/CoreTextNode.js'; +import type { + BaseShaderController, + ShaderController, +} from './ShaderController.js'; +import type { INode, INodeProps, ITextNode, ITextNodeProps } from './INode.js'; +import type { + DynamicEffects, + DynamicShaderController, +} from './DynamicShaderController.js'; +import type { + EffectDesc, + EffectDescUnion, +} from '../core/renderers/webgl/shaders/effects/ShaderEffect.js'; +import type { TextureMemoryManagerSettings } from '../core/TextureMemoryManager.js'; + +/** + * An immutable reference to a specific Shader type + * + * @remarks + * See {@link ShaderRef} for more details. + */ +export interface SpecificShaderRef { + readonly descType: 'shader'; + readonly shType: ShType; + readonly props: ExtractProps; +} + +type MapShaderRefs = + ShType extends keyof ShaderMap ? SpecificShaderRef : never; + +/** + * An immutable reference to a Shader + * + * @remarks + * This structure should only be created by the RendererMain's `createShader` + * method. The structure is immutable and should not be modified once created. + * + * A `ShaderRef` exists in the Main API Space and is used to point to an actual + * `Shader` instance in the Core API Space. The `ShaderRef` is used to + * communicate with the Core API Space to create, load, and destroy the + * `Shader` instance. + * + * This type is technically a discriminated union of all possible shader types. + * If you'd like to represent a specific shader type, you can use the + * `SpecificShaderRef` generic type. + */ +export type ShaderRef = MapShaderRefs; + +/** + * Configuration settings for {@link RendererMain} + */ +export interface RendererMainSettings { + /** + * Authored logical pixel width of the application + * + * @defaultValue `1920` + */ + appWidth?: number; + + /** + * Authored logical pixel height of the application + * + * @defaultValue `1080` + */ + appHeight?: number; + + /** + * Texture Memory Manager Settings + */ + textureMemory?: Partial; + + /** + * Bounds margin to extend the boundary in which a Node is added as Quad. + */ + boundsMargin?: number | [number, number, number, number]; + + /** + * Factor to convert app-authored logical coorindates to device logical coordinates + * + * @remarks + * This value allows auto-scaling to support larger/small resolutions than the + * app was authored for. + * + * If the app was authored for 1920x1080 and this value is 2, the app's canvas + * will be rendered at 3840x2160 logical pixels. + * + * Likewise, if the app was authored for 1920x1080 and this value is 0.66667, + * the app's canvas will be rendered at 1280x720 logical pixels. + * + * @defaultValue `1` + */ + deviceLogicalPixelRatio?: number; + + /** + * Factor to convert device logical coordinates to device physical coordinates + * + * @remarks + * This value allows auto-scaling to support devices with different pixel densities. + * + * This controls the number of physical pixels that are used to render each logical + * pixel. For example, if the device has a pixel density of 2, each logical pixel + * will be rendered using 2x2 physical pixels. + * + * By default, it will be set to `window.devicePixelRatio` which is the pixel + * density of the device the app is running on reported by the browser. + * + * @defaultValue `window.devicePixelRatio` + */ + devicePhysicalPixelRatio?: number; + + /** + * RGBA encoded number of the background to use + * + * @defaultValue `0x00000000` + */ + clearColor?: number; + + /** + * Interval in milliseconds to receive FPS updates + * + * @remarks + * If set to `0`, FPS updates will be disabled. + * + * @defaultValue `0` (disabled) + */ + fpsUpdateInterval?: number; + + /** + * Include context call (i.e. WebGL) information in FPS updates + * + * @remarks + * When enabled the number of calls to each context method over the + * `fpsUpdateInterval` will be included in the FPS update payload's + * `contextSpyData` property. + * + * Enabling the context spy has a serious impact on performance so only use it + * when you need to extract context call information. + * + * @defaultValue `false` (disabled) + */ + enableContextSpy?: boolean; + + /** + * Number or Image Workers to use + * + * @remarks + * On devices with multiple cores, this can be used to improve image loading + * as well as reduce the impact of image loading on the main thread. + * Set to 0 to disable image workers. + * + * @defaultValue `2` + */ + numImageWorkers?: number; + + /** + * Enable inspector + * + * @remarks + * When enabled the renderer will spawn a inspector. The inspector will + * replicate the state of the Nodes created in the renderer and allow + * inspection of the state of the nodes. + * + * @defaultValue `false` (disabled) + */ + enableInspector?: boolean; + + /** + * Renderer mode + */ + renderMode?: 'webgl' | 'canvas'; + + /** + * Quad buffer size in bytes + * + * @defaultValue 4 * 1024 * 1024 + */ + quadBufferSize?: number; +} + +/** + * The Renderer Main API + * + * @remarks + * This is the primary class used to configure and operate the Renderer. + * + * It is used to create and destroy Nodes, as well as Texture and Shader + * references. + * + * Example: + * ```ts + * import { RendererMain, MainCoreDriver } from '@lightningjs/renderer'; + * + * // Initialize the Renderer + * const renderer = new RendererMain( + * { + * appWidth: 1920, + * appHeight: 1080 + * }, + * 'app', + * new MainCoreDriver(), + * ); + * ``` + * + * ## Events + * - `fpsUpdate` + * - Emitted every `fpsUpdateInterval` milliseconds with the current FPS + * - `frameTick` + * - Emitted every frame tick + * - `idle` + * - Emitted when the renderer is idle (no changes to the scene + * graph/animations running) + * - `criticalCleanup` + * - Emitted when the Texture Memory Manager Cleanup process is triggered + * - Payload: { memUsed: number, criticalThreshold: number } + * - `memUsed` - The amount of memory (in bytes) used by textures before the + * cleanup process + * - `criticalThreshold` - The critical threshold (in bytes) + * - `criticalCleanupFailed` + * - Emitted when the Texture Memory Manager Cleanup process is unable to free + * up enough texture memory to reach below the critical threshold. + * This can happen when there is not enough non-renderable textures to + * free up. + * - Payload (object with keys): + * - `memUsed` - The amount of memory (in bytes) used by textures after + * the cleanup process + * - `criticalThreshold` - The critical threshold (in bytes) + */ +export class RendererMain extends EventEmitter { + readonly root: INode>; + readonly canvas: HTMLCanvasElement; + readonly settings: Readonly>; + readonly stage: Stage; + private inspector: Inspector | null = null; + + /** + * Constructs a new Renderer instance + * + * @param settings Renderer settings + * @param target Element ID or HTMLElement to insert the canvas into + * @param driver Core Driver to use + */ + constructor(settings: RendererMainSettings, target: string | HTMLElement) { + super(); + const resolvedTxSettings: TextureMemoryManagerSettings = { + criticalThreshold: settings.textureMemory?.criticalThreshold || 124e6, + targetThresholdLevel: settings.textureMemory?.targetThresholdLevel || 0.5, + cleanupInterval: settings.textureMemory?.cleanupInterval || 30000, + debugLogging: settings.textureMemory?.debugLogging || false, + }; + const resolvedSettings: Required = { + appWidth: settings.appWidth || 1920, + appHeight: settings.appHeight || 1080, + textureMemory: resolvedTxSettings, + boundsMargin: settings.boundsMargin || 0, + deviceLogicalPixelRatio: settings.deviceLogicalPixelRatio || 1, + devicePhysicalPixelRatio: + settings.devicePhysicalPixelRatio || window.devicePixelRatio, + clearColor: settings.clearColor ?? 0x00000000, + fpsUpdateInterval: settings.fpsUpdateInterval || 0, + numImageWorkers: + settings.numImageWorkers !== undefined ? settings.numImageWorkers : 2, + enableContextSpy: settings.enableContextSpy ?? false, + enableInspector: settings.enableInspector ?? false, + renderMode: settings.renderMode ?? 'webgl', + quadBufferSize: settings.quadBufferSize ?? 4 * 1024 * 1024, + }; + this.settings = resolvedSettings; + + const { + appWidth, + appHeight, + deviceLogicalPixelRatio, + devicePhysicalPixelRatio, + enableInspector, + } = resolvedSettings; + + const deviceLogicalWidth = appWidth * deviceLogicalPixelRatio; + const deviceLogicalHeight = appHeight * deviceLogicalPixelRatio; + + const canvas = document.createElement('canvas'); + this.canvas = canvas; + canvas.width = deviceLogicalWidth * devicePhysicalPixelRatio; + canvas.height = deviceLogicalHeight * devicePhysicalPixelRatio; + + canvas.style.width = `${deviceLogicalWidth}px`; + canvas.style.height = `${deviceLogicalHeight}px`; + + // Initialize the stage + this.stage = new Stage({ + appWidth: this.settings.appWidth, + appHeight: this.settings.appHeight, + boundsMargin: this.settings.boundsMargin, + clearColor: this.settings.clearColor, + canvas: this.canvas, + deviceLogicalPixelRatio: this.settings.deviceLogicalPixelRatio, + devicePhysicalPixelRatio: this.settings.devicePhysicalPixelRatio, + enableContextSpy: this.settings.enableContextSpy, + fpsUpdateInterval: this.settings.fpsUpdateInterval, + numImageWorkers: this.settings.numImageWorkers, + renderMode: this.settings.renderMode, + textureMemory: resolvedTxSettings, + eventBus: this, + quadBufferSize: this.settings.quadBufferSize, + }); + + // Extract the root node + this.root = this.stage.root as unknown as INode< + ShaderController<'DefaultShader'> + >; + + // Get the target element and attach the canvas to it + let targetEl: HTMLElement | null; + if (typeof target === 'string') { + targetEl = document.getElementById(target); + } else { + targetEl = target; + } + + if (!targetEl) { + throw new Error('Could not find target element'); + } + + targetEl.appendChild(canvas); + + // Initialize inspector (if enabled) + if (enableInspector && !isProductionEnvironment()) { + this.inspector = new Inspector(canvas, resolvedSettings); + } + } + + /** + * Create a new scene graph node + * + * @remarks + * A node is the main graphical building block of the Renderer scene graph. It + * can be a container for other nodes, or it can be a leaf node that renders a + * solid color, gradient, image, or specific texture, using a specific shader. + * + * To create a text node, see {@link createTextNode}. + * + * See {@link CoreNode} for more details. + * + * @param props + * @returns + */ + createNode< + ShCtr extends BaseShaderController = ShaderController<'DefaultShader'>, + >(props: Partial>): INode { + assertTruthy(this.stage, 'Stage is not initialized'); + + const node = this.stage.createNode(props as Partial); + + if (this.inspector) { + return this.inspector.createNode(node) as unknown as INode; + } + + // FIXME onDestroy event? node.once('beforeDestroy' + // FIXME onCreate event? + return node as unknown as INode; + } + + /** + * Create a new scene graph text node + * + * @remarks + * A text node is the second graphical building block of the Renderer scene + * graph. It renders text using a specific text renderer that is automatically + * chosen based on the font requested and what type of fonts are installed + * into an app. + * + * See {@link ITextNode} for more details. + * + * @param props + * @returns + */ + createTextNode(props: Partial): ITextNode { + const textNode = this.stage.createTextNode(props as CoreTextNodeProps); + + if (this.inspector) { + return this.inspector.createTextNode(textNode); + } + + return textNode as unknown as ITextNode; + } + + /** + * Destroy a node + * + * @remarks + * This method destroys a node + * + * @param node + * @returns + */ + destroyNode(node: INode) { + if (this.inspector) { + this.inspector.destroyNode(node.id); + } + + return node.destroy(); + } + + /** + * Create a new texture reference + * + * @remarks + * This method creates a new reference to a texture. The texture is not + * loaded until it is used on a node. + * + * It can be assigned to a node's `texture` property, or it can be used + * when creating a SubTexture. + * + * @param textureType + * @param props + * @param options + * @returns + */ + createTexture( + textureType: TxType, + props: ExtractProps, + ): InstanceType { + return this.stage.txManager.loadTexture(textureType, props); + } + + /** + * Create a new shader controller for a shader type + * + * @remarks + * This method creates a new Shader Controller for a specific shader type. + * + * If the shader has not been loaded yet, it will be loaded. Otherwise, the + * existing shader will be reused. + * + * It can be assigned to a Node's `shader` property. + * + * @param shaderType + * @param props + * @returns + */ + createShader( + shaderType: ShType, + props?: ExtractProps, + ): ShaderController { + return this.stage.shManager.loadShader(shaderType, props); + } + + /** + * Create a new Dynamic Shader controller + * + * @remarks + * A Dynamic Shader is a shader that can be composed of an array of mulitple + * effects. Each effect can be animated or changed after creation (provided + * the effect is given a name). + * + * Example: + * ```ts + * renderer.createNode({ + * shader: renderer.createDynamicShader([ + * renderer.createEffect('radius', { + * radius: 0 + * }, 'effect1'), + * renderer.createEffect('border', { + * color: 0xff00ffff, + * width: 10, + * }, 'effect2'), + * ]), + * }); + * ``` + * + * @param effects + * @returns + */ + createDynamicShader< + T extends DynamicEffects<[...{ name?: string; type: keyof EffectMap }[]]>, + >(effects: [...T]): DynamicShaderController { + return this.stage.shManager.loadDynamicShader({ + effects: effects as EffectDescUnion[], + }); + } + + /** + * Create an effect to be used in a Dynamic Shader + * + * @remark + * The {name} parameter is optional but required if you want to animate the effect + * or change the effect's properties after creation. + * + * See {@link createDynamicShader} for an example. + * + * @param type + * @param props + * @param name + * @returns + */ + createEffect< + Type extends keyof EffectMap, + Name extends string | undefined = undefined, + >( + type: Type, + props: EffectDesc<{ name: Name; type: Type }>['props'], + name?: Name, + ): EffectDesc<{ name: Name; type: Type }> { + return { + name, + type, + props, + }; + } + + /** + * Get a Node by its ID + * + * @param id + * @returns + */ + getNodeById(id: number): CoreNode | null { + const root = this.stage?.root; + if (!root) { + return null; + } + + const findNode = (node: CoreNode): CoreNode | null => { + if (node.id === id) { + return node; + } + + for (const child of node.children) { + const found = findNode(child); + if (found) { + return found; + } + } + + return null; + }; + + return findNode(root); + } + + toggleFreeze() { + throw new Error('Not implemented'); + } + + advanceFrame() { + throw new Error('Not implemented'); + } + + getBufferInfo() { + return this.stage.renderer.getBufferInfo(); + } + + /** + * Re-render the current frame without advancing any running animations. + * + * @remarks + * Any state changes will be reflected in the re-rendered frame. Useful for + * debugging. + * + * May not do anything if the render loop is running on a separate worker. + */ + rerender() { + throw new Error('Not implemented'); + } +} diff --git a/src/main-api/RendererMain.ts b/src/main-api/RendererMain.ts deleted file mode 100644 index ff2bcfbd..00000000 --- a/src/main-api/RendererMain.ts +++ /dev/null @@ -1,685 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/no-unused-vars */ -import type { ShaderMap } from '../core/CoreShaderManager.js'; -import type { - ExtractProps, - TextureMap, - TextureOptions, -} from '../core/CoreTextureManager.js'; -import type { - INode, - INodeWritableProps, - ITextNode, - ITextNodeWritableProps, -} from './INode.js'; -import type { ICoreDriver } from './ICoreDriver.js'; -import { - ManualCountTextureUsageTracker, - type ManualCountTextureUsageTrackerOptions, -} from './texture-usage-trackers/ManualCountTextureUsageTracker.js'; -import { FinalizationRegistryTextureUsageTracker } from './texture-usage-trackers/FinalizationRegistryTextureUsageTracker.js'; -import type { TextureUsageTracker } from './texture-usage-trackers/TextureUsageTracker.js'; -import { EventEmitter } from '../common/EventEmitter.js'; -import { Inspector } from './Inspector.js'; -import { santizeCustomDataMap } from '../render-drivers/utils.js'; -import { isProductionEnvironment } from '../utils.js'; -import type { StageOptions } from '../core/Stage.js'; - -/** - * An immutable reference to a specific Texture type - * - * @remarks - * See {@link TextureRef} for more details. - */ -export interface SpecificTextureRef { - readonly descType: 'texture'; - readonly txType: TxType; - readonly props: ExtractProps; - readonly options?: Readonly; -} - -type MapTextureRefs = - TxType extends keyof TextureMap ? SpecificTextureRef : never; - -/** - * An immutable reference to a Texture - * - * @remarks - * This structure should only be created by the RendererMain's `createTexture` - * method. The structure is immutable and should not be modified once created. - * - * A `TextureRef` exists in the Main API Space and is used to point to an actual - * `Texture` instance in the Core API Space. The `TextureRef` is used to - * communicate with the Core API Space to create, load, and destroy the - * `Texture` instance. - * - * This type is technically a discriminated union of all possible texture types. - * If you'd like to represent a specific texture type, you can use the - * `SpecificTextureRef` generic type. - */ -export type TextureRef = MapTextureRefs; - -/** - * An immutable reference to a specific Shader type - * - * @remarks - * See {@link ShaderRef} for more details. - */ -export interface SpecificShaderRef { - readonly descType: 'shader'; - readonly shType: ShType; - readonly props: ExtractProps; -} - -type MapShaderRefs = - ShType extends keyof ShaderMap ? SpecificShaderRef : never; - -/** - * An immutable reference to a Shader - * - * @remarks - * This structure should only be created by the RendererMain's `createShader` - * method. The structure is immutable and should not be modified once created. - * - * A `ShaderRef` exists in the Main API Space and is used to point to an actual - * `Shader` instance in the Core API Space. The `ShaderRef` is used to - * communicate with the Core API Space to create, load, and destroy the - * `Shader` instance. - * - * This type is technically a discriminated union of all possible shader types. - * If you'd like to represent a specific shader type, you can use the - * `SpecificShaderRef` generic type. - */ -export type ShaderRef = MapShaderRefs; - -/** - * Configuration settings for {@link RendererMain} - */ -export interface RendererMainSettings { - /** - * Authored logical pixel width of the application - * - * @defaultValue `1920` - */ - appWidth?: number; - - /** - * Authored logical pixel height of the application - * - * @defaultValue `1080` - */ - appHeight?: number; - - /** - * Texture Memory Byte Threshold - * - * @remarks - * When the amount of GPU VRAM used by textures exceeds this threshold, - * the Renderer will free up all the textures that are current not visible - * within the configured `boundsMargin`. - * - * When set to `0`, the threshold-based texture memory manager is disabled. - */ - txMemByteThreshold?: number; - - /** - * Bounds margin to extend the boundary in which a CoreNode is added as Quad. - */ - boundsMargin?: number | [number, number, number, number]; - - /** - * Factor to convert app-authored logical coorindates to device logical coordinates - * - * @remarks - * This value allows auto-scaling to support larger/small resolutions than the - * app was authored for. - * - * If the app was authored for 1920x1080 and this value is 2, the app's canvas - * will be rendered at 3840x2160 logical pixels. - * - * Likewise, if the app was authored for 1920x1080 and this value is 0.66667, - * the app's canvas will be rendered at 1280x720 logical pixels. - * - * @defaultValue `1` - */ - deviceLogicalPixelRatio?: number; - - /** - * Factor to convert device logical coordinates to device physical coordinates - * - * @remarks - * This value allows auto-scaling to support devices with different pixel densities. - * - * This controls the number of physical pixels that are used to render each logical - * pixel. For example, if the device has a pixel density of 2, each logical pixel - * will be rendered using 2x2 physical pixels. - * - * By default, it will be set to `window.devicePixelRatio` which is the pixel - * density of the device the app is running on reported by the browser. - * - * @defaultValue `window.devicePixelRatio` - */ - devicePhysicalPixelRatio?: number; - - /** - * RGBA encoded number of the background to use - * - * @defaultValue `0x00000000` - */ - clearColor?: number; - - /** - * Path to a custom core module to use - * - * @defaultValue `null` - */ - coreExtensionModule?: string | null; - - /** - * Enable experimental FinalizationRegistry-based texture usage tracking - * for texture garbage collection - * - * @remarks - * By default, the Renderer uses a manual reference counting system to track - * texture usage. Textures are eventually released from the Core Texture - * Manager's Usage Cache when they are no longer referenced by any Nodes (or - * SubTextures that are referenced by nodes). This works well enough, but has - * the consequence of textures being removed from Usage Cache even if their - * references are still alive in memory. This can require a texture to be - * reloaded from the source when it is used again after being removed from - * cache. - * - * This is an experimental feature that uses a FinalizationRegistry to track - * texture usage. This causes textures to be removed from the Usage Cache only - * when their references are no longer alive in memory. Meaning a loaded texture - * will remain in the Usage Cache until it's reference is garbage collected. - * - * This feature is not enabled by default because browser support for the - * FinalizationRegistry is limited. It should NOT be enabled in production apps - * as this behavior is not guaranteed to be supported in the future. Developer - * feedback on this feature, however, is welcome. - * - * @defaultValue `false` - */ - experimental_FinalizationRegistryTextureUsageTracker?: boolean; - - textureCleanupOptions?: ManualCountTextureUsageTrackerOptions; - - /** - * Interval in milliseconds to receive FPS updates - * - * @remarks - * If set to `0`, FPS updates will be disabled. - * - * @defaultValue `0` (disabled) - */ - fpsUpdateInterval?: number; - - /** - * Include context call (i.e. WebGL) information in FPS updates - * - * @remarks - * When enabled the number of calls to each context method over the - * `fpsUpdateInterval` will be included in the FPS update payload's - * `contextSpyData` property. - * - * Enabling the context spy has a serious impact on performance so only use it - * when you need to extract context call information. - * - * @defaultValue `false` (disabled) - */ - enableContextSpy?: boolean; - - /** - * Number or Image Workers to use - * - * @remarks - * On devices with multiple cores, this can be used to improve image loading - * as well as reduce the impact of image loading on the main thread. - * Set to 0 to disable image workers. - * - * @defaultValue `2` - */ - numImageWorkers?: number; - - /** - * Enable inspector - * - * @remarks - * When enabled the renderer will spawn a inspector. The inspector will - * replicate the state of the Nodes created in the renderer and allow - * inspection of the state of the nodes. - * - * @defaultValue `false` (disabled) - */ - enableInspector?: boolean; - - /** - * Renderer mode - */ - renderMode?: 'webgl' | 'canvas'; -} - -/** - * The Renderer Main API - * - * @remarks - * This is the primary class used to configure and operate the Renderer. - * - * It is used to create and destroy Nodes, as well as Texture and Shader - * references. - * - * Example: - * ```ts - * import { RendererMain, MainCoreDriver } from '@lightningjs/renderer'; - * - * // Initialize the Renderer - * const renderer = new RendererMain( - * { - * appWidth: 1920, - * appHeight: 1080 - * }, - * 'app', - * new MainCoreDriver(), - * ); - * ``` - */ -export class RendererMain extends EventEmitter { - readonly root: INode | null = null; - readonly driver: ICoreDriver; - readonly canvas: HTMLCanvasElement; - readonly settings: Readonly>; - private inspector: Inspector | null = null; - private nodes: Map = new Map(); - private nextTextureId = 1; - - /** - * Texture Usage Tracker for Usage Based Texture Garbage Collection - * - * @remarks - * For internal use only. DO NOT ACCESS. - */ - public textureTracker: TextureUsageTracker; - - /** - * Constructs a new Renderer instance - * - * @param settings Renderer settings - * @param target Element ID or HTMLElement to insert the canvas into - * @param driver Core Driver to use - */ - constructor( - settings: RendererMainSettings, - target: string | HTMLElement, - driver: ICoreDriver, - ) { - super(); - const resolvedSettings: Required = { - appWidth: settings.appWidth || 1920, - appHeight: settings.appHeight || 1080, - txMemByteThreshold: settings.txMemByteThreshold || 124e6, - boundsMargin: settings.boundsMargin || 0, - deviceLogicalPixelRatio: settings.deviceLogicalPixelRatio || 1, - devicePhysicalPixelRatio: - settings.devicePhysicalPixelRatio || window.devicePixelRatio, - clearColor: settings.clearColor ?? 0x00000000, - coreExtensionModule: settings.coreExtensionModule || null, - experimental_FinalizationRegistryTextureUsageTracker: - settings.experimental_FinalizationRegistryTextureUsageTracker ?? false, - textureCleanupOptions: settings.textureCleanupOptions || {}, - fpsUpdateInterval: settings.fpsUpdateInterval || 0, - numImageWorkers: - settings.numImageWorkers !== undefined ? settings.numImageWorkers : 2, - enableContextSpy: settings.enableContextSpy ?? false, - enableInspector: settings.enableInspector ?? false, - renderMode: settings.renderMode ?? 'webgl', - }; - this.settings = resolvedSettings; - - const { - appWidth, - appHeight, - deviceLogicalPixelRatio, - devicePhysicalPixelRatio, - enableInspector, - } = resolvedSettings; - - const releaseCallback = (textureId: number) => { - this.driver.releaseTexture(textureId); - }; - - const useFinalizationRegistryTracker = - resolvedSettings.experimental_FinalizationRegistryTextureUsageTracker && - typeof FinalizationRegistry === 'function'; - this.textureTracker = useFinalizationRegistryTracker - ? new FinalizationRegistryTextureUsageTracker(releaseCallback) - : new ManualCountTextureUsageTracker( - releaseCallback, - this.settings.textureCleanupOptions, - ); - - const deviceLogicalWidth = appWidth * deviceLogicalPixelRatio; - const deviceLogicalHeight = appHeight * deviceLogicalPixelRatio; - - this.driver = driver; - - const canvas = document.createElement('canvas'); - this.canvas = canvas; - canvas.width = deviceLogicalWidth * devicePhysicalPixelRatio; - canvas.height = deviceLogicalHeight * devicePhysicalPixelRatio; - - canvas.style.width = `${deviceLogicalWidth}px`; - canvas.style.height = `${deviceLogicalHeight}px`; - - let targetEl: HTMLElement | null; - if (typeof target === 'string') { - targetEl = document.getElementById(target); - } else { - targetEl = target; - } - - if (!targetEl) { - throw new Error('Could not find target element'); - } - - // Hook up the driver's callbacks - driver.onCreateNode = (node) => { - this.nodes.set(node.id, node); - }; - - driver.onBeforeDestroyNode = (node) => { - this.nodes.delete(node.id); - }; - - driver.onFpsUpdate = (fpsData) => { - this.emit('fpsUpdate', fpsData); - }; - - driver.onFrameTick = (frameTickData) => { - this.emit('frameTick', frameTickData); - }; - - driver.onIdle = () => { - this.emit('idle'); - }; - - targetEl.appendChild(canvas); - - if (enableInspector && !isProductionEnvironment()) { - this.inspector = new Inspector(canvas, resolvedSettings); - } - } - - /** - * Initialize the renderer - * - * @remarks - * This method must be called and resolved asyncronously before any other - * methods are called. - */ - async init(): Promise { - await this.driver.init(this, this.settings, this.canvas); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - (this.root as INode) = this.driver.getRootNode(); - } - - /** - * Create a new scene graph node - * - * @remarks - * A node is the main graphical building block of the Renderer scene graph. It - * can be a container for other nodes, or it can be a leaf node that renders a - * solid color, gradient, image, or specific texture, using a specific shader. - * - * To create a text node, see {@link createTextNode}. - * - * See {@link INode} for more details. - * - * @param props - * @returns - */ - createNode(props: Partial): INode { - if (this.inspector) { - return this.inspector.createNode( - this.driver, - this.resolveNodeDefaults(props), - ); - } - - return this.driver.createNode(this.resolveNodeDefaults(props)); - } - - /** - * Create a new scene graph text node - * - * @remarks - * A text node is the second graphical building block of the Renderer scene - * graph. It renders text using a specific text renderer that is automatically - * chosen based on the font requested and what type of fonts are installed - * into an app via a CoreExtension. - * - * See {@link ITextNode} for more details. - * - * @param props - * @returns - */ - createTextNode(props: Partial): ITextNode { - const fontSize = props.fontSize ?? 16; - const data = { - ...this.resolveNodeDefaults(props), - text: props.text ?? '', - textRendererOverride: props.textRendererOverride ?? null, - fontSize, - fontFamily: props.fontFamily ?? 'sans-serif', - fontStyle: props.fontStyle ?? 'normal', - fontWeight: props.fontWeight ?? 'normal', - fontStretch: props.fontStretch ?? 'normal', - textAlign: props.textAlign ?? 'left', - contain: props.contain ?? 'none', - scrollable: props.scrollable ?? false, - scrollY: props.scrollY ?? 0, - offsetY: props.offsetY ?? 0, - letterSpacing: props.letterSpacing ?? 0, - lineHeight: props.lineHeight, // `undefined` is a valid value - maxLines: props.maxLines ?? 0, - textBaseline: props.textBaseline ?? 'alphabetic', - verticalAlign: props.verticalAlign ?? 'middle', - overflowSuffix: props.overflowSuffix ?? '...', - debug: props.debug ?? {}, - }; - - if (this.inspector) { - return this.inspector.createTextNode(this.driver, data); - } - - return this.driver.createTextNode(data); - } - - /** - * Resolves the default property values for a Node - * - * @remarks - * This method is used internally by the RendererMain to resolve the default - * property values for a Node. It is exposed publicly so that it can be used - * by Core Driver implementations. - * - * @param props - * @returns - */ - resolveNodeDefaults(props: Partial): INodeWritableProps { - const color = props.color ?? 0xffffffff; - const colorTl = props.colorTl ?? props.colorTop ?? props.colorLeft ?? color; - const colorTr = - props.colorTr ?? props.colorTop ?? props.colorRight ?? color; - const colorBl = - props.colorBl ?? props.colorBottom ?? props.colorLeft ?? color; - const colorBr = - props.colorBr ?? props.colorBottom ?? props.colorRight ?? color; - const data = santizeCustomDataMap(props.data ?? {}); - - return { - x: props.x ?? 0, - y: props.y ?? 0, - width: props.width ?? 0, - height: props.height ?? 0, - alpha: props.alpha ?? 1, - autosize: props.autosize ?? false, - clipping: props.clipping ?? false, - color, - colorTop: props.colorTop ?? color, - colorBottom: props.colorBottom ?? color, - colorLeft: props.colorLeft ?? color, - colorRight: props.colorRight ?? color, - colorBl, - colorBr, - colorTl, - colorTr, - zIndex: props.zIndex ?? 0, - zIndexLocked: props.zIndexLocked ?? 0, - parent: props.parent ?? null, - texture: props.texture ?? null, - shader: props.shader ?? null, - // Since setting the `src` will trigger a texture load, we need to set it after - // we set the texture. Otherwise, problems happen. - src: props.src ?? '', - scale: props.scale ?? null, - scaleX: props.scaleX ?? props.scale ?? 1, - scaleY: props.scaleY ?? props.scale ?? 1, - mount: props.mount ?? 0, - mountX: props.mountX ?? props.mount ?? 0, - mountY: props.mountY ?? props.mount ?? 0, - pivot: props.pivot ?? 0.5, - pivotX: props.pivotX ?? props.pivot ?? 0.5, - pivotY: props.pivotY ?? props.pivot ?? 0.5, - rotation: props.rotation ?? 0, - rtt: props.rtt ?? false, - data: data, - }; - } - - /** - * Destroy a node - * - * @remarks - * This method destroys a node but does not destroy its children. - * - * @param node - * @returns - */ - destroyNode(node: INode) { - if (this.inspector) { - this.inspector.destroyNode(node); - } - - return this.driver.destroyNode(node); - } - - /** - * Create a new texture reference - * - * @remarks - * This method creates a new reference to a texture. The texture is not - * loaded until it is used on a node. - * - * It can be assigned to a node's `texture` property, or it can be used - * when creating a SubTexture. - * - * @param textureType - * @param props - * @param options - * @returns - */ - createTexture( - textureType: TxType, - props: SpecificTextureRef['props'], - options?: TextureOptions, - ): SpecificTextureRef { - const id = this.nextTextureId++; - const desc = { - descType: 'texture', - txType: textureType, - props, - options: { - ...options, - // This ID is used to identify the texture in the CoreTextureManager's - // ID Texture Map cache. - id, - }, - } satisfies SpecificTextureRef; - this.textureTracker.registerTexture(desc as TextureRef); - return desc; - } - - /** - * Create a new shader reference - * - * @remarks - * This method creates a new reference to a shader. The shader is not - * loaded until it is used on a Node. - * - * It can be assigned to a Node's `shader` property. - * - * @param shaderType - * @param props - * @returns - */ - createShader( - shaderType: ShType, - props?: SpecificShaderRef['props'], - ): SpecificShaderRef { - return { - descType: 'shader', - shType: shaderType, - props: props as SpecificShaderRef['props'], - }; - } - - /** - * Get a Node by its ID - * - * @param id - * @returns - */ - getNodeById(id: number): INode | null { - return this.nodes.get(id) || null; - } - - toggleFreeze() { - throw new Error('Not implemented'); - } - - advanceFrame() { - throw new Error('Not implemented'); - } - - /** - * Re-render the current frame without advancing any running animations. - * - * @remarks - * Any state changes will be reflected in the re-rendered frame. Useful for - * debugging. - * - * May not do anything if the render loop is running on a separate worker. - */ - rerender() { - throw new Error('Not implemented'); - } -} diff --git a/src/main-api/ShaderController.ts b/src/main-api/ShaderController.ts new file mode 100644 index 00000000..eb6f1de4 --- /dev/null +++ b/src/main-api/ShaderController.ts @@ -0,0 +1,81 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ShaderMap } from '../core/CoreShaderManager.js'; +import type { ExtractProps } from '../core/CoreTextureManager.js'; +import type { Stage } from '../core/Stage.js'; +import type { CoreShader } from '../core/renderers/CoreShader.js'; + +/** + * Shader Controller Base Interface + * + * @remarks + * Used directly this interface is like an `any` type for Shader Controllers. + * But it is also used as a base for more specific Shader Controller interfaces. + */ +export interface BaseShaderController { + type: keyof ShaderMap; + shader: CoreShader; + props: Record; + getResolvedProps: () => Record; +} + +/** + * Shader Controller Class + * + * @remarks + * This class is used to control shader props. + */ + +export class ShaderController + implements BaseShaderController +{ + private resolvedProps: ExtractProps; + props: ExtractProps; + constructor( + readonly type: S, + readonly shader: InstanceType, + props: ExtractProps, + stage: Stage, + ) { + this.resolvedProps = props; + + const keys = Object.keys(props); + const l = keys.length; + + const definedProps = {}; + for (let i = 0; i < l; i++) { + const name = keys[i]!; + Object.defineProperty(definedProps, name, { + get: () => { + return this.resolvedProps[name as keyof ExtractProps]; + }, + set: (value) => { + this.resolvedProps[name as keyof ExtractProps] = value; + stage.requestRender(); + }, + }); + } + this.props = definedProps as ExtractProps; + } + + getResolvedProps() { + return this.resolvedProps; + } +} diff --git a/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.ts b/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.ts deleted file mode 100644 index 1000fcf1..00000000 --- a/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assertTruthy } from '../../utils.js'; -import type { TextureRef } from '../RendererMain.js'; -import { TextureUsageTracker } from './TextureUsageTracker.js'; - -export class FinalizationRegistryTextureUsageTracker extends TextureUsageTracker { - private registry: FinalizationRegistry; - - constructor(releaseCallback: (textureDescId: number) => void) { - super(releaseCallback); - this.registry = new FinalizationRegistry(releaseCallback); - } - - override registerTexture(texture: TextureRef): void { - assertTruthy( - texture.options?.id, - 'Texture must have an ID to be registered', - ); - this.registry.register(texture, texture.options?.id); - } - override incrementTextureRefCount(): void { - // No-op for FinalizationRegistry - } - override decrementTextureRefCount(): void { - // No-op for FinalizationRegistry - } -} diff --git a/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.ts b/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.ts deleted file mode 100644 index 5d6e63d5..00000000 --- a/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assertTruthy } from '../../utils.js'; -import type { TextureRef } from '../RendererMain.js'; -import { TextureUsageTracker } from './TextureUsageTracker.js'; - -interface TextureRefInfo { - /** - * Texture Reference ID - */ - id: number; - /** - * The number of references to this texture that are currently assigned to - * Nodes. - */ - nodeRefCount: number; - /** - * The last time the texture reference count was updated. - * - * @remarks - * This is used to determine when a texture is no longer referenced by any - * Nodes and can be removed from the GPU. - */ - lastUpdate: number; -} - -export interface ManualCountTextureUsageTrackerOptions { - /** - * The interval at which to check if textures that are no longer referenced - * by any Nodes can be released from the Core Space Texture Usage Cache. - * - * @remarks - * Only valid when the {@link ManualCountTextureUsageTracker} is used. - * - * @defaultValue 10000 (10 seconds) - */ - textureCleanupIntervalMs?: number; - /** - * The age at which a texture is considered to be no longer referenced by any - * Nodes and can be released from the Core Space Texture Usage Cache. - * - * @remarks - * Only valid when the {@link ManualCountTextureUsageTracker} is used. - * - * @defaultValue 60000 (1 minute) - */ - textureCleanupAgeThreadholdMs?: number; -} - -/** - * Usage-based Texture Garbage Collection Registry - */ -export class ManualCountTextureUsageTracker extends TextureUsageTracker { - textureMap: Map = new Map(); - zeroReferenceTextureSet: Set = new Set(); - options: Required; - - constructor( - releaseCallback: (textureDescId: number) => void, - options: ManualCountTextureUsageTrackerOptions, - ) { - super(releaseCallback); - this.options = { - textureCleanupIntervalMs: options.textureCleanupIntervalMs ?? 10000, - textureCleanupAgeThreadholdMs: - options.textureCleanupAgeThreadholdMs ?? 60000, - }; - // Periodically check for textures that are no longer referenced by any - // Nodes and notify RendererMain to release them. - setInterval(() => { - const now = Date.now(); - const thresholdMs = this.options.textureCleanupAgeThreadholdMs; - for (const textureRefInfo of this.zeroReferenceTextureSet) { - if (now - textureRefInfo.lastUpdate > thresholdMs) { - this.releaseCallback(textureRefInfo.id); - this.textureMap.delete(textureRefInfo.id); - this.zeroReferenceTextureSet.delete(textureRefInfo); - } - } - }, this.options.textureCleanupIntervalMs); - } - - registerTexture(texture: TextureRef) { - const textureId = texture.options?.id; - assertTruthy(textureId, 'Texture must have an id to be registered'); - if (!this.textureMap.has(textureId)) { - const textureRefInfo: TextureRefInfo = { - id: textureId, - nodeRefCount: 0, - lastUpdate: Date.now(), - }; - this.textureMap.set(textureId, textureRefInfo); - this.zeroReferenceTextureSet.add(textureRefInfo); - } - } - - incrementTextureRefCount(texture: TextureRef) { - const textureId = texture.options?.id; - assertTruthy(textureId, 'Texture must have an id to be registered'); - let textureRefInfo = this.textureMap.get(textureId); - if (!textureRefInfo) { - // Texture has not been registered yet, so register it now. - // This may happen if the TextureRef was cleaned up from the registry - // but was still alive in memory and eventually re-used. - this.registerTexture(texture); - textureRefInfo = this.textureMap.get(textureId); - } - assertTruthy(textureRefInfo, 'Texture must have been registered'); - if (texture.txType === 'SubTexture') { - // If this is a SubTexture, then increment the reference count of the - // parent texture as well. - this.incrementTextureRefCount(texture.props.texture); - } - textureRefInfo.nodeRefCount++; - textureRefInfo.lastUpdate = Date.now(); - if (this.zeroReferenceTextureSet.has(textureRefInfo)) { - this.zeroReferenceTextureSet.delete(textureRefInfo); - } - } - - decrementTextureRefCount(texture: TextureRef) { - const textureId = texture.options?.id; - assertTruthy(textureId, 'Texture must have an id to be registered'); - const textureRefInfo = this.textureMap.get(textureId); - assertTruthy(textureRefInfo, 'Texture must have been registered'); - textureRefInfo.nodeRefCount--; - textureRefInfo.lastUpdate = Date.now(); - if (textureRefInfo.nodeRefCount === 0) { - this.zeroReferenceTextureSet.add(textureRefInfo); - } - if (texture.txType === 'SubTexture') { - // If this is a SubTexture, then decrement the reference count of the - // parent texture as well. - this.decrementTextureRefCount(texture.props.texture); - } - } -} diff --git a/src/main-api/texture-usage-trackers/TextureUsageTracker.ts b/src/main-api/texture-usage-trackers/TextureUsageTracker.ts deleted file mode 100644 index 3fe6bd98..00000000 --- a/src/main-api/texture-usage-trackers/TextureUsageTracker.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { TextureRef } from '../RendererMain.js'; - -/** - * Texture Usage Tracker for Usage Based Texture Garbage Collection - */ -export abstract class TextureUsageTracker { - constructor(protected releaseCallback: (textureDescId: number) => void) {} - - /** - * Register a texture with the tracker. - * - * @param texture - */ - abstract registerTexture(texture: TextureRef): void; - - /** - * Increment the reference count for a texture. - * - * @remarks - * This should be called anytime a Node sets a new texture. - * - * @param texture - */ - abstract incrementTextureRefCount(texture: TextureRef): void; - - /** - * Decrement the Node reference count for a texture. - * - * @remarks - * This should be called anytime a Node removes a texture. - * - * @param texture - */ - abstract decrementTextureRefCount(texture: TextureRef): void; -} diff --git a/src/main-api/utils.ts b/src/main-api/utils.ts new file mode 100644 index 00000000..1403fd79 --- /dev/null +++ b/src/main-api/utils.ts @@ -0,0 +1,45 @@ +import type { CustomDataMap } from '../core/CoreNode.js'; + +export function santizeCustomDataMap(d: CustomDataMap): CustomDataMap { + const validTypes = { + boolean: true, + string: true, + number: true, + undefined: true, + }; + + const keys = Object.keys(d); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!key) { + continue; + } + + const value = d[key]; + const valueType = typeof value; + + // Typescript doesn't understand the above const valueType ¯\_(ツ)_/¯ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore-next-line + if (valueType === 'string' && value.length > 2048) { + console.warn( + `Custom Data value for ${key} is too long, it will be truncated to 2048 characters`, + ); + + // same here, see above comment, this can only be a string at this point + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore-next-line + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + d[key] = value.substring(0, 2048); + } + + if (!validTypes[valueType as keyof typeof validTypes]) { + console.warn( + `Custom Data value for ${key} is not a boolean, string, or number, it will be ignored`, + ); + delete d[key]; + } + } + + return d; +} diff --git a/src/render-drivers/main/MainCoreDriver.ts b/src/render-drivers/main/MainCoreDriver.ts deleted file mode 100644 index 0890a5ee..00000000 --- a/src/render-drivers/main/MainCoreDriver.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assertTruthy } from '../../utils.js'; -import type { ICoreDriver } from '../../main-api/ICoreDriver.js'; -import type { - INode, - INodeWritableProps, - ITextNodeWritableProps, -} from '../../main-api/INode.js'; -import { MainOnlyNode, getNewId } from './MainOnlyNode.js'; -import { - Stage, - type StageFpsUpdateHandler, - type StageFrameTickHandler, -} from '../../core/Stage.js'; -import type { - RendererMain, - RendererMainSettings, -} from '../../main-api/RendererMain.js'; -import { MainOnlyTextNode } from './MainOnlyTextNode.js'; -import { loadCoreExtension } from '../utils.js'; -import type { - FpsUpdatePayload, - FrameTickPayload, -} from '../../common/CommonTypes.js'; - -export class MainCoreDriver implements ICoreDriver { - private root: MainOnlyNode | null = null; - private stage: Stage | null = null; - private rendererMain: RendererMain | null = null; - - async init( - rendererMain: RendererMain, - rendererSettings: Required, - canvas: HTMLCanvasElement, - ): Promise { - this.stage = new Stage({ - rootId: getNewId(), - appWidth: rendererSettings.appWidth, - appHeight: rendererSettings.appHeight, - txMemByteThreshold: rendererSettings.txMemByteThreshold, - boundsMargin: rendererSettings.boundsMargin, - deviceLogicalPixelRatio: rendererSettings.deviceLogicalPixelRatio, - devicePhysicalPixelRatio: rendererSettings.devicePhysicalPixelRatio, - clearColor: rendererSettings.clearColor, - canvas, - fpsUpdateInterval: rendererSettings.fpsUpdateInterval, - enableContextSpy: rendererSettings.enableContextSpy, - numImageWorkers: rendererSettings.numImageWorkers, - renderMode: rendererSettings.renderMode, - debug: { - monitorTextureCache: false, - }, - }); - this.rendererMain = rendererMain; - assertTruthy(this.stage.root); - const node = new MainOnlyNode( - rendererMain.resolveNodeDefaults({}), - this.rendererMain, - this.stage, - this.stage.root, - ); - this.root = node; - node.once('beforeDestroy', this.onBeforeDestroyNode.bind(this, node)); - this.onCreateNode(node); - - // Load the Core Extension Module if one was specified. - if (rendererSettings.coreExtensionModule) { - await loadCoreExtension(rendererSettings.coreExtensionModule, this.stage); - } - - // Forward fpsUpdate events from the stage to RendererMain - this.stage.on('fpsUpdate', ((stage, fpsData) => { - this.onFpsUpdate(fpsData); - }) satisfies StageFpsUpdateHandler); - - this.stage.on('frameTick', ((stage, frameTickData) => { - this.onFrameTick(frameTickData); - }) satisfies StageFrameTickHandler); - - this.stage.on('idle', () => { - this.onIdle(); - }); - } - - createNode(props: INodeWritableProps): INode { - assertTruthy(this.rendererMain); - assertTruthy(this.stage); - const node = new MainOnlyNode(props, this.rendererMain, this.stage); - node.once('beforeDestroy', this.onBeforeDestroyNode.bind(this, node)); - this.onCreateNode(node); - return node; - } - - createTextNode(props: ITextNodeWritableProps) { - assertTruthy(this.rendererMain); - assertTruthy(this.stage); - const node = new MainOnlyTextNode(props, this.rendererMain, this.stage); - node.once('beforeDestroy', this.onBeforeDestroyNode.bind(this, node)); - this.onCreateNode(node); - return node; - } - - // TODO: Remove? - destroyNode(node: INode): void { - node.destroy(); - } - - releaseTexture(id: number): void { - const { stage } = this; - assertTruthy(stage); - stage.txManager.removeTextureIdFromCache(id); - } - - getRootNode(): INode { - assertTruthy(this.root); - return this.root; - } - - //#region Event Methods - // The implementations for these event methods are provided by RendererMain - onCreateNode(node: INode): void { - throw new Error('Method not implemented.'); - } - - onBeforeDestroyNode(node: INode): void { - throw new Error('Method not implemented.'); - } - - onFpsUpdate(fpsData: FpsUpdatePayload) { - throw new Error('Method not implemented.'); - } - - onFrameTick(frameTickData: FrameTickPayload) { - throw new Error('Method not implemented.'); - } - - onIdle() { - throw new Error('Method not implemented.'); - } - //#endregion -} diff --git a/src/render-drivers/main/MainOnlyNode.ts b/src/render-drivers/main/MainOnlyNode.ts deleted file mode 100644 index 07f19e7f..00000000 --- a/src/render-drivers/main/MainOnlyNode.ts +++ /dev/null @@ -1,553 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { - CustomDataMap, - INode, - INodeAnimatableProps, - INodeWritableProps, -} from '../../main-api/INode.js'; -import type { Stage } from '../../core/Stage.js'; -import { assertTruthy } from '../../utils.js'; -import type { IAnimationController } from '../../common/IAnimationController.js'; -import { CoreAnimation } from '../../core/animations/CoreAnimation.js'; -import { CoreAnimationController } from '../../core/animations/CoreAnimationController.js'; -import { CoreNode } from '../../core/CoreNode.js'; -import type { - RendererMain, - ShaderRef, - TextureRef, -} from '../../main-api/RendererMain.js'; -import type { AnimationSettings } from '../../core/animations/CoreAnimation.js'; -import { EventEmitter } from '../../common/EventEmitter.js'; -import type { - NodeLoadedEventHandler, - NodeFailedEventHandler, - NodeRenderStateEventHandler, -} from '../../common/CommonTypes.js'; -import { santizeCustomDataMap } from '../utils.js'; - -let nextId = 1; - -export function getNewId(): number { - return nextId++; -} - -export class MainOnlyNode extends EventEmitter implements INode { - readonly id; - protected coreNode: CoreNode; - - // Prop stores - protected _children: MainOnlyNode[] = []; - protected _src = ''; - protected _parent: MainOnlyNode | null = null; - protected _texture: TextureRef | null = null; - protected _shader: ShaderRef | null = null; - protected _data: CustomDataMap | undefined = {}; - - constructor( - props: INodeWritableProps, - private rendererMain: RendererMain, - private stage: Stage, - coreNode?: CoreNode, - ) { - super(); - this.id = coreNode?.id ?? getNewId(); - this.coreNode = - coreNode || - new CoreNode(this.stage, { - id: this.id, - x: props.x, - y: props.y, - width: props.width, - height: props.height, - alpha: props.alpha, - autosize: props.autosize, - clipping: props.clipping, - color: props.color, - colorTop: props.colorTop, - colorBottom: props.colorBottom, - colorLeft: props.colorLeft, - colorRight: props.colorRight, - colorTl: props.colorTl, - colorTr: props.colorTr, - colorBl: props.colorBl, - colorBr: props.colorBr, - zIndex: props.zIndex, - zIndexLocked: props.zIndexLocked, - scaleX: props.scaleX, - scaleY: props.scaleY, - mountX: props.mountX, - mountY: props.mountY, - mount: props.mount, - pivot: props.pivot, - pivotX: props.pivotX, - pivotY: props.pivotY, - rotation: props.rotation, - parent: null, - shader: null, - shaderProps: null, - texture: null, - textureOptions: null, - rtt: props.rtt, - }); - // Forward loaded/failed events - this.coreNode.on('loaded', this.onTextureLoaded); - this.coreNode.on('failed', this.onTextureFailed); - this.coreNode.on('freed', this.onTextureFreed); - - this.coreNode.on('outOfBounds', this.onOutOfBounds); - this.coreNode.on('inBounds', this.onInBounds); - this.coreNode.on('outOfViewport', this.onOutOfViewport); - this.coreNode.on('inViewport', this.onInViewport); - - // Assign properties to this object - this.parent = props.parent as MainOnlyNode; - this.shader = props.shader; - this.texture = props.texture; - this.src = props.src; - this.rtt = props.rtt; - this._data = props.data; - } - - get x(): number { - return this.coreNode.x; - } - - set x(value: number) { - this.coreNode.x = value; - } - - get y(): number { - return this.coreNode.y; - } - - set y(value: number) { - this.coreNode.y = value; - } - - get width(): number { - return this.coreNode.width; - } - - set width(value: number) { - if (value !== this.coreNode.width && this.coreNode.rtt) { - this.texture = this.rendererMain.createTexture( - 'RenderTexture', - { - width: this.width, - height: this.height, - }, - { preload: true, flipY: true }, - ); - } - this.coreNode.width = value; - } - - get height(): number { - return this.coreNode.height; - } - - set height(value: number) { - if (value !== this.coreNode.height && this.coreNode.rtt) { - this.texture = this.rendererMain.createTexture( - 'RenderTexture', - { - width: this.width, - height: this.height, - }, - { preload: true, flipY: true }, - ); - } - this.coreNode.height = value; - } - - get alpha(): number { - return this.coreNode.alpha; - } - - set alpha(value: number) { - this.coreNode.alpha = value; - } - - get autosize(): boolean { - return this.coreNode.autosize; - } - - set autosize(value: boolean) { - this.coreNode.autosize = value; - } - - get clipping(): boolean { - return this.coreNode.clipping; - } - - set clipping(value: boolean) { - this.coreNode.clipping = value; - } - - get color(): number { - return this.coreNode.color; - } - - set color(value: number) { - this.coreNode.color = value; - } - - get colorTop(): number { - return this.coreNode.colorTop; - } - - set colorTop(value: number) { - this.coreNode.colorTop = value; - } - - get colorBottom(): number { - return this.coreNode.colorBottom; - } - - set colorBottom(value: number) { - this.coreNode.colorBottom = value; - } - - get colorLeft(): number { - return this.coreNode.colorLeft; - } - - set colorLeft(value: number) { - this.coreNode.colorLeft = value; - } - - get colorRight(): number { - return this.coreNode.colorRight; - } - - set colorRight(value: number) { - this.coreNode.colorRight = value; - } - - get colorTl(): number { - return this.coreNode.colorTl; - } - - set colorTl(value: number) { - this.coreNode.colorTl = value; - } - - get colorTr(): number { - return this.coreNode.colorTr; - } - - set colorTr(value: number) { - this.coreNode.colorTr = value; - } - - get colorBl(): number { - return this.coreNode.colorBl; - } - - set colorBl(value: number) { - this.coreNode.colorBl = value; - } - - get colorBr(): number { - return this.coreNode.colorBr; - } - - set colorBr(value: number) { - this.coreNode.colorBr = value; - } - - get scale(): number | null { - if (this.scaleX !== this.scaleY) { - return null; - } - return this.coreNode.scaleX; - } - - set scale(value: number | null) { - // We ignore `null` when it's set. - if (value === null) { - return; - } - this.coreNode.scaleX = value; - this.coreNode.scaleY = value; - } - - get scaleX(): number { - return this.coreNode.scaleX; - } - - set scaleX(value: number) { - this.coreNode.scaleX = value; - } - - get scaleY(): number { - return this.coreNode.scaleY; - } - - set scaleY(value: number) { - this.coreNode.scaleY = value; - } - - get mount(): number { - return this.coreNode.mount; - } - - set mount(value: number) { - this.coreNode.mount = value; - } - - get mountX(): number { - return this.coreNode.mountX; - } - - set mountX(value: number) { - this.coreNode.mountX = value; - } - - get mountY(): number { - return this.coreNode.mountY; - } - - set mountY(value: number) { - this.coreNode.mountY = value; - } - - get pivot(): number { - return this.coreNode.pivot; - } - - set pivot(value: number) { - this.coreNode.pivot = value; - } - - get pivotX(): number { - return this.coreNode.pivotX; - } - - set pivotX(value: number) { - this.coreNode.pivotX = value; - } - - get pivotY(): number { - return this.coreNode.pivotY; - } - - set pivotY(value: number) { - this.coreNode.pivotY = value; - } - - get rotation(): number { - return this.coreNode.rotation; - } - - set rotation(value: number) { - this.coreNode.rotation = value; - } - - get parent(): MainOnlyNode | null { - return this._parent; - } - - set parent(newParent: MainOnlyNode | null) { - const oldParent = this._parent; - this._parent = newParent; - this.coreNode.parent = newParent?.coreNode ?? null; - if (oldParent) { - const index = oldParent.children.indexOf(this); - assertTruthy( - index !== -1, - "MainOnlyNode.parent: Node not found in old parent's children!", - ); - oldParent.children.splice(index, 1); - } - if (newParent) { - newParent.children.push(this); - } - } - - get children(): MainOnlyNode[] { - return this._children; - } - - get zIndex(): number { - return this.coreNode.zIndex; - } - - set zIndex(value: number) { - this.coreNode.zIndex = value; - } - - get zIndexLocked(): number { - return this.coreNode.zIndexLocked; - } - - set zIndexLocked(value: number) { - this.coreNode.zIndexLocked = value; - } - - get src(): string { - return this._src; - } - - set src(imageUrl: string) { - if (this._src === imageUrl) { - return; - } - this._src = imageUrl; - if (!imageUrl) { - this.texture = null; - return; - } - this.texture = this.rendererMain.createTexture('ImageTexture', { - src: imageUrl, - }); - } - - //#region Texture - get texture(): TextureRef | null { - return this._texture; - } - - set texture(texture: TextureRef | null) { - if (this._texture === texture) { - return; - } - if (this._texture) { - this.rendererMain.textureTracker.decrementTextureRefCount(this._texture); - } - if (texture) { - this.rendererMain.textureTracker.incrementTextureRefCount(texture); - } - this._texture = texture; - if (texture) { - this.coreNode.loadTexture(texture.txType, texture.props, texture.options); - } else { - this.coreNode.unloadTexture(); - } - } - - get rtt(): boolean { - return this.coreNode.rtt; - } - - set rtt(value: boolean) { - if (value) { - this.texture = this.rendererMain.createTexture( - 'RenderTexture', - { - width: this.width, - height: this.height, - }, - { preload: true, flipY: true }, - ); - } - this.coreNode.rtt = value; - } - - get parentHasRenderTexture(): boolean { - return this.coreNode.parentHasRenderTexture; - } - - private onTextureLoaded: NodeLoadedEventHandler = (target, payload) => { - this.emit('loaded', payload); - }; - - private onTextureFailed: NodeFailedEventHandler = (target, payload) => { - this.emit('failed', payload); - }; - - private onTextureFreed: NodeLoadedEventHandler = (target, payload) => { - this.emit('freed', payload); - }; - - private onOutOfBounds: NodeRenderStateEventHandler = (target, payload) => { - this.emit('outOfBounds', payload); - }; - - private onInBounds: NodeRenderStateEventHandler = (target, payload) => { - this.emit('inBounds', payload); - }; - - private onOutOfViewport: NodeRenderStateEventHandler = (target, payload) => { - this.emit('outOfViewport', payload); - }; - - private onInViewport: NodeRenderStateEventHandler = (target, payload) => { - this.emit('inViewport', payload); - }; - //#endregion Texture - - get shader(): ShaderRef | null { - return this._shader; - } - - set shader(shader: ShaderRef | null) { - if (this._shader === shader) { - return; - } - this._shader = shader; - if (shader) { - this.coreNode.loadShader(shader.shType, shader.props); - } - } - - get data(): CustomDataMap | undefined { - return this._data; - } - - set data(d: CustomDataMap) { - this._data = santizeCustomDataMap(d); - } - - destroy(): void { - this.emit('beforeDestroy', {}); - - //use while loop since setting parent to null removes it from array - let child = this.children[0]; - while (child) { - child.destroy(); - child = this.children[0]; - } - this.coreNode.destroy(); - this.parent = null; - this.texture = null; - this.emit('afterDestroy', {}); - this.removeAllListeners(); - } - - flush(): void { - // No-op - } - - animate( - props: Partial, - settings: Partial, - ): IAnimationController { - const animation = new CoreAnimation(this.coreNode, props, settings); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - const controller = new CoreAnimationController( - this.stage.animationManager, - animation, - ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return controller; - } -} diff --git a/src/render-drivers/main/MainOnlyTextNode.ts b/src/render-drivers/main/MainOnlyTextNode.ts deleted file mode 100644 index 239d4230..00000000 --- a/src/render-drivers/main/MainOnlyTextNode.ts +++ /dev/null @@ -1,261 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { - ITextNode, - ITextNodeWritableProps, -} from '../../main-api/INode.js'; -import type { Stage } from '../../core/Stage.js'; -import type { RendererMain } from '../../main-api/RendererMain.js'; -import { MainOnlyNode, getNewId } from './MainOnlyNode.js'; -import { CoreTextNode } from '../../core/CoreTextNode.js'; - -export class MainOnlyTextNode extends MainOnlyNode implements ITextNode { - protected declare coreNode: CoreTextNode; - - constructor( - props: ITextNodeWritableProps, - rendererMain: RendererMain, - stage: Stage, - ) { - super( - props, - rendererMain, - stage, - new CoreTextNode(stage, { - id: getNewId(), - x: props.x, - y: props.y, - width: props.width, - height: props.height, - alpha: props.alpha, - autosize: props.autosize, - clipping: props.clipping, - color: props.color, - colorTop: props.colorTop, - colorBottom: props.colorBottom, - colorLeft: props.colorLeft, - colorRight: props.colorRight, - colorTl: props.colorTl, - colorTr: props.colorTr, - colorBl: props.colorBl, - colorBr: props.colorBr, - zIndex: props.zIndex, - zIndexLocked: props.zIndexLocked, - scaleX: props.scaleX, - scaleY: props.scaleY, - mountX: props.mountX, - mountY: props.mountY, - mount: props.mount, - pivot: props.pivot, - pivotX: props.pivotX, - pivotY: props.pivotY, - rotation: props.rotation, - - // Text properties - text: props.text, - fontSize: props.fontSize, - fontFamily: props.fontFamily, - fontWeight: props.fontWeight, - fontStretch: props.fontStretch, - fontStyle: props.fontStyle, - contain: props.contain, - scrollable: props.scrollable, - letterSpacing: props.letterSpacing, - textAlign: props.textAlign, - scrollY: props.scrollY, - offsetY: props.offsetY, - textRendererOverride: props.textRendererOverride, - lineHeight: props.lineHeight, - maxLines: props.maxLines, - textBaseline: props.textBaseline, - verticalAlign: props.verticalAlign, - overflowSuffix: props.overflowSuffix, - debug: props.debug, - // These properties will get set appropriately in the base MainOnlyNode class - parent: null, - texture: null, - textureOptions: null, - shader: null, - shaderProps: null, - rtt: false, - }), - ); - } - - get text(): string { - return this.coreNode.text; - } - - set text(value: string) { - this.coreNode.text = value; - } - - get textRendererOverride(): ITextNode['textRendererOverride'] { - return this.coreNode.textRendererOverride; - } - - set textRendererOverride(value: ITextNode['textRendererOverride']) { - this.coreNode.textRendererOverride = value; - } - - get fontSize(): number { - return this.coreNode.fontSize; - } - - set fontSize(value: number) { - this.coreNode.fontSize = value; - } - - get fontFamily(): ITextNode['fontFamily'] { - return this.coreNode.fontFamily; - } - - set fontFamily(value: ITextNode['fontFamily']) { - this.coreNode.fontFamily = value; - } - - get fontWeight(): ITextNode['fontWeight'] { - return this.coreNode.fontWeight; - } - - set fontWeight(value: ITextNode['fontWeight']) { - this.coreNode.fontWeight = value; - } - - get fontStretch(): ITextNode['fontStretch'] { - return this.coreNode.fontStretch; - } - - set fontStretch(value: ITextNode['fontStretch']) { - this.coreNode.fontStretch = value; - } - - get fontStyle(): ITextNode['fontStyle'] { - return this.coreNode.fontStyle; - } - - set fontStyle(value: ITextNode['fontStyle']) { - this.coreNode.fontStyle = value; - } - - get textAlign(): ITextNode['textAlign'] { - return this.coreNode.textAlign; - } - - set textAlign(value: ITextNode['textAlign']) { - this.coreNode.textAlign = value; - } - - get contain(): ITextNode['contain'] { - return this.coreNode.contain; - } - - set contain(value: ITextNode['contain']) { - this.coreNode.contain = value; - } - - get scrollable(): ITextNode['scrollable'] { - return this.coreNode.scrollable; - } - - set scrollable(value: ITextNode['scrollable']) { - this.coreNode.scrollable = value; - } - - get scrollY(): ITextNode['scrollY'] { - return this.coreNode.scrollY; - } - - set scrollY(value: ITextNode['scrollY']) { - this.coreNode.scrollY = value; - } - - get offsetY(): ITextNode['offsetY'] { - return this.coreNode.offsetY; - } - - set offsetY(value: ITextNode['offsetY']) { - this.coreNode.offsetY = value; - } - - get letterSpacing(): ITextNode['letterSpacing'] { - return this.coreNode.letterSpacing; - } - - set letterSpacing(value: ITextNode['letterSpacing']) { - this.coreNode.letterSpacing = value; - } - - get lineHeight(): ITextNode['lineHeight'] { - return this.coreNode.lineHeight; - } - - set lineHeight(value: ITextNode['lineHeight']) { - this.coreNode.lineHeight = value; - } - - get maxLines(): ITextNode['maxLines'] { - return this.coreNode.maxLines; - } - - set maxLines(value: ITextNode['maxLines']) { - if (value) { - this.coreNode.maxLines = value; - } - } - - get textBaseline(): ITextNode['textBaseline'] { - return this.coreNode.textBaseline; - } - - set textBaseline(value: ITextNode['textBaseline']) { - if (value) { - this.coreNode.textBaseline = value; - } - } - - get verticalAlign(): ITextNode['verticalAlign'] { - return this.coreNode.verticalAlign; - } - - set verticalAlign(value: ITextNode['verticalAlign']) { - if (value) { - this.coreNode.verticalAlign = value; - } - } - - get overflowSuffix(): ITextNode['overflowSuffix'] { - return this.coreNode.overflowSuffix; - } - - set overflowSuffix(value: ITextNode['overflowSuffix']) { - if (value) { - this.coreNode.overflowSuffix = value; - } - } - - get debug(): ITextNode['debug'] { - return this.coreNode.debug; - } - - set debug(value: ITextNode['debug']) { - this.coreNode.debug = value; - } -} diff --git a/src/render-drivers/threadx/NodeStruct.ts b/src/render-drivers/threadx/NodeStruct.ts deleted file mode 100644 index 9e41ffb7..00000000 --- a/src/render-drivers/threadx/NodeStruct.ts +++ /dev/null @@ -1,320 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BufferStruct, structProp, genTypeId } from '@lightningjs/threadx'; - -export interface NodeStructWritableProps { - x: number; - y: number; - width: number; - height: number; - alpha: number; - autosize: boolean; - clipping: boolean; - color: number; - colorTop: number; - colorBottom: number; - colorLeft: number; - colorRight: number; - colorTl: number; - colorTr: number; - colorBr: number; - colorBl: number; - parentId: number; - zIndex: number; - zIndexLocked: number; - scaleX: number; - scaleY: number; - mount: number; - mountX: number; - mountY: number; - pivot: number; - pivotX: number; - pivotY: number; - rotation: number; - rtt: boolean; -} - -export class NodeStruct - extends BufferStruct - implements NodeStructWritableProps -{ - static override readonly typeId = genTypeId('NODE'); - - @structProp('number') - get x(): number { - return 0; - } - - set x(value: number) { - // Decorator will handle this - } - - @structProp('number') - get y(): number { - return 0; - } - - set y(value: number) { - // Decorator will handle this - } - - @structProp('number') - get width(): number { - return 0; - } - - set width(value: number) { - // Decorator will handle this - } - - @structProp('number') - get height(): number { - return 0; - } - - set height(value: number) { - // Decorator will handle this - } - - @structProp('number') - get alpha(): number { - return 1; - } - - set alpha(value: number) { - // Decorator will handle this - } - - @structProp('boolean') - get autosize(): boolean { - return false; - } - - set autosize(value: boolean) { - // Decorator will handle this - } - - @structProp('boolean') - get clipping(): boolean { - return false; - } - - set clipping(value: boolean) { - // Decorator will handle this - } - - @structProp('number') - get color(): number { - return 0; - } - - set color(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorTop(): number { - return 0; - } - - set colorTop(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorBottom(): number { - return 0; - } - - set colorBottom(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorLeft(): number { - return 0; - } - - set colorLeft(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorRight(): number { - return 0; - } - - set colorRight(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorTl(): number { - return 0; - } - - set colorTl(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorTr(): number { - return 0; - } - - set colorTr(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorBl(): number { - return 0; - } - - set colorBl(value: number) { - // Decorator will handle this - } - - @structProp('number') - get colorBr(): number { - return 0; - } - - set colorBr(value: number) { - // Decorator will handle this - } - - @structProp('number') - get scaleX(): number { - return 1; - } - - set scaleX(value: number) { - // Decorator will handle this - } - - @structProp('number') - get scaleY(): number { - return 1; - } - - set scaleY(value: number) { - // Decorator will handle this - } - - @structProp('number') - get mount(): number { - return 0; - } - - set mount(value: number) { - // Decorator will handle this - } - - @structProp('number') - get mountX(): number { - return 0; - } - - set mountX(value: number) { - // Decorator will handle this - } - - @structProp('number') - get mountY(): number { - return 0; - } - - set mountY(value: number) { - // Decorator will handle this - } - - @structProp('number') - get pivot(): number { - return 0.5; - } - - set pivot(value: number) { - // Decorator will handle this - } - - @structProp('number') - get pivotX(): number { - return 0.5; - } - - set pivotX(value: number) { - // Decorator will handle this - } - - @structProp('number') - get pivotY(): number { - return 0.5; - } - - set pivotY(value: number) { - // Decorator will handle this - } - - @structProp('number') - get rotation(): number { - return 0; - } - - set rotation(value: number) { - // Decorator will handle this - } - - @structProp('number') - get parentId(): number { - return 0; - } - - set parentId(value: number) { - // Decorator will handle this - } - - @structProp('number') - get zIndex(): number { - return 0; - } - - set zIndex(value: number) { - // Decorator will handle this - } - - @structProp('number') - get zIndexLocked(): number { - return 0; - } - - set zIndexLocked(value: number) { - // Decorator will handle this - } - - @structProp('boolean') - get rtt(): boolean { - return false; - } - - set rtt(value: boolean) { - // Decorator will handle this - } -} diff --git a/src/render-drivers/threadx/SharedNode.ts b/src/render-drivers/threadx/SharedNode.ts deleted file mode 100644 index c662306d..00000000 --- a/src/render-drivers/threadx/SharedNode.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { NodeStruct, NodeStructWritableProps } from './NodeStruct.js'; -import { SharedObject } from '@lightningjs/threadx'; - -export class SharedNode extends SharedObject { - declare z$__type__Props: NodeStructWritableProps & - SharedObject['z$__type__Props']; - - /** - * Must have lock on sharedNode before calling constructor! - * - * @param sharedNodeStruct - */ - constructor( - sharedNodeStruct: NodeStruct, - extendedCurProps?: Record, - ) { - super(sharedNodeStruct, { - ...extendedCurProps, - x: sharedNodeStruct.x, - y: sharedNodeStruct.y, - width: sharedNodeStruct.width, - height: sharedNodeStruct.height, - alpha: sharedNodeStruct.alpha, - autosize: sharedNodeStruct.autosize, - clipping: sharedNodeStruct.clipping, - color: sharedNodeStruct.color, - colorTop: sharedNodeStruct.colorTop, - colorBottom: sharedNodeStruct.colorBottom, - colorLeft: sharedNodeStruct.colorLeft, - colorRight: sharedNodeStruct.colorRight, - colorTl: sharedNodeStruct.colorTl, - colorTr: sharedNodeStruct.colorTr, - colorBl: sharedNodeStruct.colorBl, - colorBr: sharedNodeStruct.colorBr, - parentId: sharedNodeStruct.parentId, - zIndex: sharedNodeStruct.zIndex, - zIndexLocked: sharedNodeStruct.zIndexLocked, - scaleX: sharedNodeStruct.scaleX, - scaleY: sharedNodeStruct.scaleY, - mount: sharedNodeStruct.mount, - mountX: sharedNodeStruct.mountX, - mountY: sharedNodeStruct.mountY, - pivot: sharedNodeStruct.pivot, - pivotX: sharedNodeStruct.pivotX, - pivotY: sharedNodeStruct.pivotY, - rotation: sharedNodeStruct.rotation, - rtt: sharedNodeStruct.rtt, - } satisfies NodeStructWritableProps); - } - - // Declare getters and setters for all properties that are automatically - // generated on this class. - declare x: number; - declare y: number; - declare width: number; - declare height: number; - declare alpha: number; - declare autosize: boolean; - declare clipping: boolean; - declare color: number; - declare colorTop: number; - declare colorBottom: number; - declare colorLeft: number; - declare colorRight: number; - declare colorTl: number; - declare colorTr: number; - declare colorBl: number; - declare colorBr: number; - declare scaleX: number; - declare scaleY: number; - declare mountX: number; - declare mountY: number; - declare mount: number; - declare pivot: number; - declare pivotX: number; - declare pivotY: number; - declare rotation: number; - protected declare parentId: number; - declare zIndex: number; - declare zIndexLocked: number; - declare rtt: boolean; -} diff --git a/src/render-drivers/threadx/TextNodeStruct.ts b/src/render-drivers/threadx/TextNodeStruct.ts deleted file mode 100644 index 92732788..00000000 --- a/src/render-drivers/threadx/TextNodeStruct.ts +++ /dev/null @@ -1,213 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { structProp, genTypeId } from '@lightningjs/threadx'; -import { NodeStruct, type NodeStructWritableProps } from './NodeStruct.js'; -import type { - TextRendererMap, - TrProps, -} from '../../core/text-rendering/renderers/TextRenderer.js'; - -export interface TextNodeStructWritableProps - extends NodeStructWritableProps, - Omit { - textRendererOverride: keyof TextRendererMap | null; -} - -export class TextNodeStruct - extends NodeStruct - implements TextNodeStructWritableProps -{ - static override readonly typeId = genTypeId('TEXT'); - - @structProp('string') - get text(): string { - return ''; - } - - set text(value: string) { - // Decorator will handle this - } - - @structProp('string', { - propToBuffer(value: TextNodeStructWritableProps['textRendererOverride']) { - // Property accepts `null` but the buffer only accepts a string. - // Encode `null` as a special string - return value ?? '$$null'; - }, - bufferToProp(value: string) { - return value === '$$null' ? null : (value as keyof TextRendererMap); - }, - }) - get textRendererOverride(): TextNodeStructWritableProps['textRendererOverride'] { - return null; - } - - set textRendererOverride( - value: TextNodeStructWritableProps['textRendererOverride'], - ) { - // Decorator will handle this - } - - @structProp('number') - get fontSize(): number { - return 0; - } - - set fontSize(value: number) { - // Decorator will handle this - } - - @structProp('string') - get fontFamily(): TextNodeStructWritableProps['fontFamily'] { - return ''; - } - - set fontFamily(value: TextNodeStructWritableProps['fontFamily']) { - // Decorator will handle this - } - - @structProp('string') - get fontStretch(): TextNodeStructWritableProps['fontStretch'] { - return 'normal'; - } - - set fontStretch(value: TextNodeStructWritableProps['fontStretch']) { - // Decorator will handle this - } - - @structProp('string') - get fontStyle(): TextNodeStructWritableProps['fontStyle'] { - return 'normal'; - } - - set fontStyle(value: TextNodeStructWritableProps['fontStyle']) { - // Decorator will handle this - } - - @structProp('string') - get fontWeight(): TextNodeStructWritableProps['fontWeight'] { - return 'normal'; - } - - set fontWeight(value: TextNodeStructWritableProps['fontWeight']) { - // Decorator will handle this - } - - @structProp('string') - get textAlign(): TextNodeStructWritableProps['textAlign'] { - return 'left'; - } - - set textAlign(value: TextNodeStructWritableProps['textAlign']) { - // Decorator will handle this - } - - @structProp('string') - get contain(): TextNodeStructWritableProps['contain'] { - return 'none'; - } - - set contain(value: TextNodeStructWritableProps['contain']) { - // Decorator will handle this - } - - @structProp('boolean') - get scrollable(): TextNodeStructWritableProps['scrollable'] { - return false; - } - - set scrollable(value: TextNodeStructWritableProps['scrollable']) { - // Decorator will handle this - } - - @structProp('number') - get scrollY(): TextNodeStructWritableProps['scrollY'] { - return 0; - } - - set scrollY(value: TextNodeStructWritableProps['scrollY']) { - // Decorator will handle this - } - - @structProp('number') - get offsetY(): TextNodeStructWritableProps['offsetY'] { - return 0; - } - - set offsetY(value: TextNodeStructWritableProps['offsetY']) { - // Decorator will handle this - } - - @structProp('number') - get letterSpacing(): TextNodeStructWritableProps['letterSpacing'] { - return 0; - } - - set letterSpacing(value: TextNodeStructWritableProps['letterSpacing']) { - // Decorator will handle this - } - - @structProp('number', { - allowUndefined: true, - }) - get lineHeight(): TextNodeStructWritableProps['lineHeight'] { - return 0; - } - - set lineHeight(value: TextNodeStructWritableProps['lineHeight']) { - // Decorator will handle this - } - - @structProp('number') - get maxLines(): TextNodeStructWritableProps['maxLines'] { - return 0; - } - - set maxLines(value: TextNodeStructWritableProps['maxLines']) { - // Decorator will handle this - } - - @structProp('string') - get textBaseline(): TextNodeStructWritableProps['textBaseline'] { - return 'alphabetic'; - } - - set textBaseline(value: TextNodeStructWritableProps['textBaseline']) { - // Decorator will handle this - } - - @structProp('string') - get verticalAlign(): TextNodeStructWritableProps['verticalAlign'] { - return 'middle'; - } - - set verticalAlign(value: TextNodeStructWritableProps['verticalAlign']) { - // Decorator will handle this - } - - @structProp('string') - get overflowSuffix(): TextNodeStructWritableProps['overflowSuffix'] { - return '...'; - } - - set overflowSuffix(value: TextNodeStructWritableProps['overflowSuffix']) { - // Decorator will handle this - } -} diff --git a/src/render-drivers/threadx/ThreadXCoreDriver.ts b/src/render-drivers/threadx/ThreadXCoreDriver.ts deleted file mode 100644 index fd496652..00000000 --- a/src/render-drivers/threadx/ThreadXCoreDriver.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ThreadX, BufferStruct } from '@lightningjs/threadx'; -import type { - INode, - INodeWritableProps, - ITextNode, - ITextNodeWritableProps, -} from '../../main-api/INode.js'; -import { NodeStruct, type NodeStructWritableProps } from './NodeStruct.js'; -import type { ICoreDriver } from '../../main-api/ICoreDriver.js'; -import { ThreadXMainNode } from './ThreadXMainNode.js'; -import { assertTruthy } from '../../utils.js'; -import type { - RendererMain, - RendererMainSettings, -} from '../../main-api/RendererMain.js'; -import { - isThreadXRendererMessage, - type ThreadXRendererInitMessage, - type ThreadXRendererReleaseTextureMessage, -} from './ThreadXRendererMessage.js'; -import { - TextNodeStruct, - type TextNodeStructWritableProps, -} from './TextNodeStruct.js'; -import { ThreadXMainTextNode } from './ThreadXMainTextNode.js'; -import type { - FpsUpdatePayload, - FrameTickPayload, -} from '../../common/CommonTypes.js'; - -export interface ThreadXRendererSettings { - coreWorkerUrl: string; -} - -export class ThreadXCoreDriver implements ICoreDriver { - private settings: ThreadXRendererSettings; - private threadx: ThreadX; - private rendererMain: RendererMain | null = null; - private root: INode | null = null; - private fps = 0; - - constructor(settings: ThreadXRendererSettings) { - this.settings = settings; - this.threadx = ThreadX.init({ - workerId: 1, - workerName: 'main', - sharedObjectFactory: (buffer) => { - const typeId = BufferStruct.extractTypeId(buffer); - const rendererMain = this.rendererMain; - assertTruthy(rendererMain); - if (typeId === NodeStruct.typeId) { - const nodeStruct = new NodeStruct(buffer); - return nodeStruct.lock(() => { - return new ThreadXMainNode(rendererMain, nodeStruct); - }); - } else if (typeId === TextNodeStruct.typeId) { - const nodeStruct = new TextNodeStruct(buffer); - return nodeStruct.lock(() => { - return new ThreadXMainTextNode(rendererMain, nodeStruct); - }); - } - return null; - }, - onMessage: async (message) => { - // Forward fpsUpdate events from the renderer worker's Stage to RendererMain - if (isThreadXRendererMessage('fpsUpdate', message)) { - this.onFpsUpdate(message.fpsData); - } - - // Forward frameTick events from the renderer worker's Stage to RendererMain - if (isThreadXRendererMessage('frameTick', message)) { - this.onFrameTick(message.frameTickData); - } - }, - }); - this.threadx.registerWorker( - 'renderer', - new Worker(this.settings.coreWorkerUrl, { type: 'module' }), - ); - } - - async init( - rendererMain: RendererMain, - rendererSettings: Required, - canvas: HTMLCanvasElement, - ): Promise { - this.rendererMain = rendererMain; - const offscreenCanvas = canvas.transferControlToOffscreen(); - const rootNodeId = (await this.threadx.sendMessageAsync( - 'renderer', - { - type: 'init', - canvas: offscreenCanvas, - appWidth: rendererSettings.appWidth, - appHeight: rendererSettings.appHeight, - txMemByteThreshold: rendererSettings.txMemByteThreshold, - boundsMargin: rendererSettings.boundsMargin, - deviceLogicalPixelRatio: rendererSettings.deviceLogicalPixelRatio, - devicePhysicalPixelRatio: rendererSettings.devicePhysicalPixelRatio, - clearColor: rendererSettings.clearColor, - coreExtensionModule: rendererSettings.coreExtensionModule, - fpsUpdateInterval: rendererSettings.fpsUpdateInterval, - enableContextSpy: rendererSettings.enableContextSpy, - numImageWorkers: rendererSettings.numImageWorkers, - } satisfies ThreadXRendererInitMessage, - [offscreenCanvas], - )) as number; - // The Render worker shares the root node with this worker during the - // 'init' call above. That call returns the ID of the root node, which - // we can use to retrieve it from the shared object store. - const rootNode = this.threadx.getSharedObjectById(rootNodeId); - assertTruthy( - rootNode instanceof ThreadXMainNode, - 'Unexpected root node type', - ); - this.root = rootNode; - } - - getRootNode(): INode { - assertTruthy(this.root, 'Driver not initialized'); - return this.root; - } - - createNode(props: INodeWritableProps): INode { - const rendererMain = this.rendererMain; - assertTruthy(rendererMain); - const bufferStruct = new NodeStruct(); - Object.assign(bufferStruct, { - // Node specific properties - x: props.x, - y: props.y, - width: props.width, - height: props.height, - parentId: props.parent ? props.parent.id : 0, - autosize: props.autosize, - clipping: props.clipping, - color: props.color, - colorTop: props.colorTop, - colorRight: props.colorBottom, - colorBottom: props.colorBottom, - colorLeft: props.colorLeft, - colorTl: props.colorTl, - colorTr: props.colorTr, - colorBl: props.colorBl, - colorBr: props.colorBr, - alpha: props.alpha, - zIndex: props.zIndex, - zIndexLocked: props.zIndexLocked, - scaleX: props.scaleX, - scaleY: props.scaleY, - mount: props.mount, - mountX: props.mountX, - mountY: props.mountY, - pivot: props.pivot, - pivotX: props.pivotX, - pivotY: props.pivotY, - rotation: props.rotation, - rtt: props.rtt, - } satisfies NodeStructWritableProps); - - const node = new ThreadXMainNode(rendererMain, bufferStruct); - node.once('beforeDestroy', this.onBeforeDestroyNode.bind(this, node)); - this.threadx.shareObjects('renderer', [node]).catch(console.error); - node.shader = props.shader ?? null; - node.texture = props.texture ?? null; - node.src = props.src ?? ''; - this.onCreateNode(node); - return node; - } - - createTextNode(props: ITextNodeWritableProps): ITextNode { - const rendererMain = this.rendererMain; - assertTruthy(rendererMain); - const bufferStruct = new TextNodeStruct(); - - Object.assign(bufferStruct, { - // Node specific properties - x: props.x, - y: props.y, - width: props.width, - height: props.height, - parentId: props.parent ? props.parent.id : 0, - clipping: props.clipping, - color: props.color, - colorTop: props.colorTop, - colorRight: props.colorBottom, - colorBottom: props.colorBottom, - colorLeft: props.colorLeft, - colorTl: props.colorTl, - colorTr: props.colorTr, - colorBl: props.colorBl, - colorBr: props.colorBr, - alpha: props.alpha, - autosize: props.autosize, - zIndex: props.zIndex, - zIndexLocked: props.zIndexLocked, - scaleX: props.scaleX, - scaleY: props.scaleY, - mount: props.mount, - mountX: props.mountX, - mountY: props.mountY, - pivot: props.pivot, - pivotX: props.pivotX, - pivotY: props.pivotY, - rotation: props.rotation, - rtt: props.rtt, - - // Text specific properties - text: props.text, - textRendererOverride: props.textRendererOverride, - fontSize: props.fontSize, - fontFamily: props.fontFamily, - fontWeight: props.fontWeight, - fontStretch: props.fontStretch, - fontStyle: props.fontStyle, - lineHeight: props.lineHeight, - maxLines: props.maxLines, - textBaseline: props.textBaseline, - verticalAlign: props.verticalAlign, - contain: props.contain, - letterSpacing: props.letterSpacing, - overflowSuffix: props.overflowSuffix, - offsetY: props.offsetY, - textAlign: props.textAlign, - scrollable: props.scrollable, - scrollY: props.scrollY, - } satisfies TextNodeStructWritableProps); - - const node = new ThreadXMainTextNode(rendererMain, bufferStruct); - node.once('beforeDestroy', this.onBeforeDestroyNode.bind(this, node)); - this.threadx.shareObjects('renderer', [node]).catch(console.error); - - node.shader = props.shader ?? null; - node.texture = props.texture ?? null; - node.src = props.src ?? ''; - node.debug = props.debug ?? {}; - - this.onCreateNode(node); - return node; - } - - // TODO: Remove? - destroyNode(node: INode): void { - node.destroy(); - } - - releaseTexture(textureDescId: number): void { - this.threadx.sendMessage('renderer', { - type: 'releaseTexture', - textureDescId, - } satisfies ThreadXRendererReleaseTextureMessage); - } - - //#region Event Methods - // The implementations for these event methods are provided by RendererMain - onCreateNode(node: INode): void { - throw new Error('Method not implemented.'); - } - - onBeforeDestroyNode(node: INode): void { - throw new Error('Method not implemented.'); - } - - onFpsUpdate(fps: FpsUpdatePayload): void { - throw new Error('Method not implemented.'); - } - - onFrameTick(frameTickData: FrameTickPayload): void { - throw new Error('Method not implemented.'); - } - //#endregion -} diff --git a/src/render-drivers/threadx/ThreadXMainAnimationController.ts b/src/render-drivers/threadx/ThreadXMainAnimationController.ts deleted file mode 100644 index 31e5f7fd..00000000 --- a/src/render-drivers/threadx/ThreadXMainAnimationController.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable @typescript-eslint/unbound-method */ -import { EventEmitter } from '../../common/EventEmitter.js'; -import type { - AnimationControllerState, - IAnimationController, -} from '../../common/IAnimationController.js'; -import { assertTruthy } from '../../utils.js'; -import type { ThreadXMainNode } from './ThreadXMainNode.js'; - -export class ThreadXMainAnimationController - extends EventEmitter - implements IAnimationController -{ - stoppedPromise: Promise; - /** - * If this is null, then the animation is in a finished / stopped state. - */ - stoppedResolve: (() => void) | null = null; - state: AnimationControllerState; - - constructor(private node: ThreadXMainNode, private id: number) { - super(); - this.state = 'stopped'; - - // Initial stopped promise is resolved (since the animation is stopped) - this.stoppedPromise = Promise.resolve(); - - // Bind event handlers - this.onAnimating = this.onAnimating.bind(this); - this.onFinished = this.onFinished.bind(this); - } - - start(): IAnimationController { - if (this.state !== 'running') { - this.makeStoppedPromise(); - this.sendStart(); - this.state = 'running'; - } - return this; - } - - stop(): IAnimationController { - if (this.state === 'stopped') { - return this; - } - this.sendStop(); - // if (this.stoppedResolve !== null) { - // this.stoppedResolve(); - // this.stoppedResolve = null; - // this.emit('stopped', this); - // } - this.state = 'stopped'; - return this; - } - - pause(): IAnimationController { - this.node.emit('pauseAnimation', { id: this.id }); - this.state = 'paused'; - return this; - } - - restore(): IAnimationController { - return this; - } - - waitUntilStopped(): Promise { - return this.stoppedPromise; - } - - private sendStart(): void { - // Hook up event listeners - this.node.on('animationFinished', this.onFinished); - this.node.on('animationAnimating', this.onAnimating); - // Then register the animation - this.node.emit('startAnimation', { id: this.id }); - } - - private sendStop(): void { - // First unregister the animation - this.node.emit('stopAnimation', { id: this.id }); - // Then unhook event listeners - this.node.off('animationFinished', this.onFinished); - this.node.off('animationAnimating', this.onAnimating); - } - - private makeStoppedPromise(): void { - if (this.stoppedResolve === null) { - this.stoppedPromise = new Promise((resolve) => { - this.stoppedResolve = resolve; - }); - } - } - - private onFinished( - target: ThreadXMainNode, - { id }: { id: number; loop: boolean }, - ) { - if (id === this.id) { - assertTruthy(this.stoppedResolve); - this.node.off('animationFinished', this.onFinished); - this.node.off('animationAnimating', this.onAnimating); - - // resolve promise - this.stoppedResolve(); - this.stoppedResolve = null; - this.emit('stopped', this); - this.state = 'stopped'; - } - } - - private onAnimating(target: ThreadXMainNode, { id }: { id: number }) { - if (id === this.id) { - this.emit('animating', this); - } - } -} diff --git a/src/render-drivers/threadx/ThreadXMainNode.ts b/src/render-drivers/threadx/ThreadXMainNode.ts deleted file mode 100644 index 54c650fd..00000000 --- a/src/render-drivers/threadx/ThreadXMainNode.ts +++ /dev/null @@ -1,201 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { IAnimationController } from '../../common/IAnimationController.js'; -import type { - CustomDataMap, - INode, - INodeAnimatableProps, -} from '../../main-api/INode.js'; -import type { - RendererMain, - ShaderRef, - TextureRef, -} from '../../main-api/RendererMain.js'; -import { assertTruthy } from '../../utils.js'; -import type { NodeStruct } from './NodeStruct.js'; -import { SharedNode } from './SharedNode.js'; -import { ThreadXMainAnimationController } from './ThreadXMainAnimationController.js'; -import type { AnimationSettings } from '../../core/animations/CoreAnimation.js'; -import { santizeCustomDataMap } from '../utils.js'; - -export class ThreadXMainNode extends SharedNode implements INode { - private nextAnimationId = 1; - protected _parent: ThreadXMainNode | null = null; - protected _children: ThreadXMainNode[] = []; - protected _texture: TextureRef | null = null; - protected _shader: ShaderRef | null = null; - protected _data: CustomDataMap | undefined = {}; - private _src = ''; - private _parentHasRenderTexture = false; - - /** - * FinalizationRegistry for animation controllers. When an animation - * controller is garbage collected, we let the render worker know so that - * it can remove it's corresponding animation controler from it's stored - * Set. This should ultimately allow the render worker to garbage collect - * it's animation controller. The animation itself independent from the animation - * controller, so it will continue to run until it's finished regardless of - * whether or not the animation controller is garbage collected. - */ - private animationRegistry = new FinalizationRegistry((id: number) => { - this.emit('destroyAnimation', { id }); - }); - - constructor( - private rendererMain: RendererMain, - sharedNodeStruct: NodeStruct, - extendedCurProps?: Record, - ) { - super(sharedNodeStruct, extendedCurProps); - } - - get texture(): TextureRef | null { - return this._texture; - } - - set texture(texture: TextureRef | null) { - if (this._texture === texture) { - return; - } - if (this._texture) { - this.rendererMain.textureTracker.decrementTextureRefCount(this._texture); - } - if (texture) { - this.rendererMain.textureTracker.incrementTextureRefCount(texture); - } - this._texture = texture; - if (texture) { - this.emit('loadTexture', texture as unknown as Record); - } else { - this.emit('unloadTexture', {}); - } - } - - get shader(): ShaderRef | null { - return this._shader; - } - - set shader(shader: ShaderRef | null) { - if (this._shader === shader) { - return; - } - this._shader = shader; - if (shader) { - this.emit('loadShader', shader as unknown as Record); - } - } - - get scale(): number | null { - if (this.scaleX !== this.scaleY) { - return null; - } - return this.scaleX; - } - - set scale(scale: number | null) { - // We ignore `null` when it's set. - if (scale === null) { - return; - } - this.scaleX = scale; - this.scaleY = scale; - } - - animate( - props: Partial, - settings: Partial, - ): IAnimationController { - const id = this.nextAnimationId++; - this.emit('createAnimation', { id, props, settings }); - const controller = new ThreadXMainAnimationController(this, id); - this.animationRegistry.register(controller, id); - return controller; - } - - get src(): string { - return this._src; - } - - set src(imageUrl: string) { - if (this._src === imageUrl) { - return; - } - this._src = imageUrl; - if (!imageUrl) { - this.texture = null; - return; - } - this.texture = this.rendererMain.createTexture('ImageTexture', { - src: imageUrl, - }); - } - - //#region Parent/Child Props - get parent(): ThreadXMainNode | null { - return this._parent; - } - - set parent(newParent: ThreadXMainNode | null) { - const oldParent = this._parent; - this._parent = newParent; - this.parentId = newParent?.id ?? 0; - if (oldParent) { - const index = oldParent.children.indexOf(this); - assertTruthy( - index !== -1, - "ThreadXMainNode.parent: Node not found in old parent's children!", - ); - oldParent.children.splice(index, 1); - } - if (newParent) { - newParent.children.push(this); - } - } - - set parentHasRenderTexture(hasRenderTexture: boolean) { - this._parentHasRenderTexture = hasRenderTexture; - } - - get parentHasRenderTexture(): boolean { - return this._parentHasRenderTexture; - } - - get children(): ThreadXMainNode[] { - return this._children; - } - //#endregion Parent/Child Props - - get props() { - return this.curProps; - } - - get data(): CustomDataMap | undefined { - return this._data; - } - - set data(d: CustomDataMap) { - this._data = santizeCustomDataMap(d); - } - - override destroy() { - super.destroy(); - this.texture = null; - } -} diff --git a/src/render-drivers/threadx/ThreadXMainTextNode.ts b/src/render-drivers/threadx/ThreadXMainTextNode.ts deleted file mode 100644 index 97c0aa72..00000000 --- a/src/render-drivers/threadx/ThreadXMainTextNode.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ITextNode } from '../../main-api/INode.js'; -import type { RendererMain } from '../../main-api/RendererMain.js'; -import type { NodeStructWritableProps } from './NodeStruct.js'; -import type { - TextNodeStruct, - TextNodeStructWritableProps, -} from './TextNodeStruct.js'; -import { ThreadXMainNode } from './ThreadXMainNode.js'; - -export class ThreadXMainTextNode extends ThreadXMainNode implements ITextNode { - protected _debug: ITextNode['debug'] = {}; - - constructor(rendererMain: RendererMain, sharedNodeStruct: TextNodeStruct) { - super(rendererMain, sharedNodeStruct, { - text: sharedNodeStruct.text, - textRendererOverride: sharedNodeStruct.textRendererOverride, - fontSize: sharedNodeStruct.fontSize, - fontFamily: sharedNodeStruct.fontFamily, - fontStretch: sharedNodeStruct.fontStretch, - fontStyle: sharedNodeStruct.fontStyle, - fontWeight: sharedNodeStruct.fontWeight, - lineHeight: sharedNodeStruct.lineHeight, - maxLines: sharedNodeStruct.maxLines, - textBaseline: sharedNodeStruct.textBaseline, - verticalAlign: sharedNodeStruct.verticalAlign, - contain: sharedNodeStruct.contain, - letterSpacing: sharedNodeStruct.letterSpacing, - offsetY: sharedNodeStruct.offsetY, - scrollable: sharedNodeStruct.scrollable, - scrollY: sharedNodeStruct.scrollY, - textAlign: sharedNodeStruct.textAlign, - overflowSuffix: sharedNodeStruct.overflowSuffix, - } satisfies Omit); - } - - declare text: ITextNode['text']; - declare textRendererOverride: ITextNode['textRendererOverride']; - declare fontSize: ITextNode['fontSize']; - declare fontFamily: ITextNode['fontFamily']; - declare fontStretch: ITextNode['fontStretch']; - declare fontStyle: ITextNode['fontStyle']; - declare fontWeight: ITextNode['fontWeight']; - declare lineHeight: ITextNode['lineHeight']; - declare maxLines: ITextNode['maxLines']; - declare textBaseline: ITextNode['textBaseline']; - declare verticalAlign: ITextNode['verticalAlign']; - declare textAlign: ITextNode['textAlign']; - declare contain: ITextNode['contain']; - declare scrollable: ITextNode['scrollable']; - declare scrollY: ITextNode['scrollY']; - declare offsetY: ITextNode['offsetY']; - declare letterSpacing: ITextNode['letterSpacing']; - declare overflowSuffix: ITextNode['overflowSuffix']; - - get debug(): ITextNode['debug'] { - return this._debug; - } - - set debug(debug: ITextNode['debug']) { - if (this._debug === debug) { - return; - } - this._debug = debug; - this.emit('debug', debug); - } -} diff --git a/src/render-drivers/threadx/ThreadXRendererMessage.ts b/src/render-drivers/threadx/ThreadXRendererMessage.ts deleted file mode 100644 index 9b813900..00000000 --- a/src/render-drivers/threadx/ThreadXRendererMessage.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { - FpsUpdatePayload, - FrameTickPayload, -} from '../../common/CommonTypes.js'; - -/** - * @module - * @description - * Message types / utils for communication between the main worker and the - * worker worker. - */ - -/** - * Defines the shape of a message sent from the main worker to the worker - */ -export interface ThreadXRendererMessage { - type: string; -} - -/** - * An initialization message sent from the main worker to the renderer worker - */ -export interface ThreadXRendererInitMessage extends ThreadXRendererMessage { - type: 'init'; - canvas: OffscreenCanvas; - appWidth: number; - appHeight: number; - txMemByteThreshold: number; - boundsMargin: number | [number, number, number, number]; - deviceLogicalPixelRatio: number; - devicePhysicalPixelRatio: number; - clearColor: number; - fpsUpdateInterval: number; - enableContextSpy: boolean; - numImageWorkers: number; - coreExtensionModule: string | null; -} - -/** - * A message sent from the main worker to the renderer worker to release a - * texture - */ -export interface ThreadXRendererReleaseTextureMessage - extends ThreadXRendererMessage { - type: 'releaseTexture'; - textureDescId: number; -} - -/** - * A message sent from the renderer worker to the main worker to update the FPS - */ -export interface ThreadXRendererFpsUpdateMessage - extends ThreadXRendererMessage { - type: 'fpsUpdate'; - fpsData: FpsUpdatePayload; -} - -/** - * A message sent from the renderer worker to the main worker to update the FPS - */ -export interface ThreadXRendererFrameTickMessage - extends ThreadXRendererMessage { - type: 'frameTick'; - frameTickData: FrameTickPayload; -} - -/** - * A map of message types to message shapes - */ -export interface ThreadXRendererMessageMap { - init: ThreadXRendererInitMessage; - releaseTexture: ThreadXRendererReleaseTextureMessage; - fpsUpdate: ThreadXRendererFpsUpdateMessage; - frameTick: ThreadXRendererFrameTickMessage; -} - -/** - * Type guard util for a message sent from the main worker to the renderer worker - * - * @param type - * @param message - * @returns - */ -export function isThreadXRendererMessage< - Type extends keyof ThreadXRendererMessageMap, ->(type: Type, message: unknown): message is ThreadXRendererMessageMap[Type] { - return ( - typeof message === 'object' && - message !== null && - 'type' in message && - message.type === type - ); -} diff --git a/src/render-drivers/threadx/worker/ThreadXRendererNode.ts b/src/render-drivers/threadx/worker/ThreadXRendererNode.ts deleted file mode 100644 index e42e0035..00000000 --- a/src/render-drivers/threadx/worker/ThreadXRendererNode.ts +++ /dev/null @@ -1,253 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { NodeStruct } from '../NodeStruct.js'; -import { SharedNode } from '../SharedNode.js'; -import { ThreadX } from '@lightningjs/threadx'; -import type { Stage } from '../../../core/Stage.js'; -import { assertTruthy } from '../../../utils.js'; -import type { IAnimationController } from '../../../common/IAnimationController.js'; -import type { INodeAnimatableProps } from '../../../main-api/INode.js'; -import { CoreAnimation } from '../../../core/animations/CoreAnimation.js'; -import { CoreAnimationController } from '../../../core/animations/CoreAnimationController.js'; -import type { Texture } from '../../../core/textures/Texture.js'; -import { CoreNode } from '../../../core/CoreNode.js'; -import type { ShaderRef, TextureRef } from '../../../main-api/RendererMain.js'; -import type { AnimationSettings } from '../../../core/animations/CoreAnimation.js'; -import type { - NodeLoadedPayload, - NodeFailedPayload, - NodeTextureFreedPayload, -} from '../../../common/CommonTypes.js'; - -export class ThreadXRendererNode extends SharedNode { - protected coreNode: CoreNode; - protected _parent: ThreadXRendererNode | null = null; - protected _children: ThreadXRendererNode[] = []; - texture: Texture | null = null; - - private animationControllers = new Map(); - - constructor( - private stage: Stage, - sharedNodeStruct: NodeStruct, - coreNode?: CoreNode, - extendedCurProps?: Record, - ) { - super(sharedNodeStruct, extendedCurProps); - // This Proxy makes sure properties on the coreNode that an animation - // changes are also updated on the shared node. - // TODO: Improve this pattern because its ugly!!! - this.coreNode = new Proxy( - coreNode || this.createCoreNode(stage, sharedNodeStruct), - { - set: (target, prop, value) => { - // Only set the numeric properties on the shared node. - if (prop !== 'parent' && prop !== 'texture' && prop !== 'shader') { - Reflect.set(this, prop, value); - } - return Reflect.set(target, prop, value); - }, - }, - ); - - // Set up parent - const parent = ThreadX.instance.getSharedObjectById( - sharedNodeStruct.parentId, - ); - assertTruthy(parent instanceof ThreadXRendererNode || parent === null); - this.parent = parent; - - // Create inbound event listeners - // TOOD: Make sure event listeners are removed when the node is destroyed. - this.on( - 'createAnimation', - (target: ThreadXRendererNode, { id, props, settings }) => { - const animation = new CoreAnimation( - this.coreNode, - props as Partial, - settings as Partial, - ); - animation.on('animating', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this.emit('animationAnimating', { - id: id as number, - }); - }); - animation.on('finished', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this.emit('animationFinished', { - id: id as number, - loop: settings.loop, - }); - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - const controller = new CoreAnimationController( - this.stage.animationManager, - animation, - ); - this.animationControllers.set(id as number, controller); - }, - ); - this.on('destroyAnimation', (target: ThreadXRendererNode, { id }) => { - this.animationControllers.delete(id as number); - }); - this.on('startAnimation', (target: ThreadXRendererNode, { id }) => { - this.animationControllers.get(id as number)?.start(); - }); - this.on('stopAnimation', (target: ThreadXRendererNode, { id }) => { - this.animationControllers.get(id as number)?.stop(); - }); - this.on('pauseAnimation', (target: ThreadXRendererNode, { id }) => { - this.animationControllers.get(id as number)?.pause(); - }); - this.on( - 'loadTexture', - (target: ThreadXRendererNode, textureDesc: TextureRef) => { - this.coreNode.loadTexture( - textureDesc.txType, - textureDesc.props, - textureDesc.options, - ); - }, - ); - this.on( - 'loadShader', - (target: ThreadXRendererNode, shaderDesc: ShaderRef) => { - this.coreNode.loadShader(shaderDesc.shType, shaderDesc.props); - }, - ); - this.on('unloadTexture', (target: ThreadXRendererNode) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - this.coreNode.unloadTexture(); - }); - // Forward on CoreNode events - this.coreNode.on( - 'loaded', - (target: CoreNode, payload: NodeLoadedPayload) => { - this.emit('loaded', payload); - }, - ); - this.coreNode.on( - 'failed', - (target: CoreNode, payload: NodeFailedPayload) => { - this.emit('failed', payload); - }, - ); - this.coreNode.on( - 'freed', - (target: CoreNode, payload: NodeTextureFreedPayload) => { - this.emit('freed', payload); - }, - ); - } - - override onPropertyChange( - propName: Key, - newValue: this['z$__type__Props'][Key], - oldValue: this['z$__type__Props'][Key] | undefined, - ): void { - if (propName === 'parentId') { - const parent = ThreadX.instance.getSharedObjectById(newValue as number); - assertTruthy(parent instanceof ThreadXRendererNode || parent === null); - this.parent = parent; - return; - } else { - // @ts-expect-error Ignore readonly assignment errors - this.coreNode[propName as keyof CoreNode] = - newValue as CoreNode[keyof CoreNode]; - } - } - - //#region Parent/Child Props - get parent(): ThreadXRendererNode | null { - return this._parent; - } - - set parent(newParent: ThreadXRendererNode | null) { - const oldParent = this._parent; - this._parent = newParent; - this.coreNode.parent = newParent?.coreNode ?? null; - this.parentId = newParent?.id ?? 0; - if (oldParent) { - const index = oldParent.children.indexOf(this); - assertTruthy( - index !== -1, - "ThreadXRendererNode.parent: Node not found in old parent's children!", - ); - oldParent.children.splice(index, 1); - } - if (newParent) { - newParent.children.push(this); - } - } - - get children(): ThreadXRendererNode[] { - return this._children; - } - //#endregion Parent/Child Props - - private createCoreNode(stage: Stage, sharedNodeStruct: NodeStruct) { - const parent = ThreadX.instance.getSharedObjectById( - sharedNodeStruct.parentId, - ); - assertTruthy(parent instanceof ThreadXRendererNode || parent === null); - const node = new CoreNode(stage, { - id: sharedNodeStruct.id, - x: sharedNodeStruct.x, - y: sharedNodeStruct.y, - width: sharedNodeStruct.width, - height: sharedNodeStruct.height, - alpha: sharedNodeStruct.alpha, - autosize: sharedNodeStruct.autosize, - clipping: sharedNodeStruct.clipping, - color: sharedNodeStruct.color, - colorTop: sharedNodeStruct.colorTop, - colorBottom: sharedNodeStruct.colorBottom, - colorLeft: sharedNodeStruct.colorLeft, - colorRight: sharedNodeStruct.colorRight, - colorTl: sharedNodeStruct.colorTl, - colorTr: sharedNodeStruct.colorTr, - colorBl: sharedNodeStruct.colorBl, - colorBr: sharedNodeStruct.colorBr, - zIndex: sharedNodeStruct.zIndex, - zIndexLocked: sharedNodeStruct.zIndexLocked, - scaleX: sharedNodeStruct.scaleX, - scaleY: sharedNodeStruct.scaleY, - mount: sharedNodeStruct.mount, - mountX: sharedNodeStruct.mountX, - mountY: sharedNodeStruct.mountY, - pivot: sharedNodeStruct.pivot, - pivotX: sharedNodeStruct.pivotX, - pivotY: sharedNodeStruct.pivotY, - rotation: sharedNodeStruct.rotation, - rtt: sharedNodeStruct.rtt, - - // These are passed in via message handlers - shader: null, - shaderProps: null, - texture: null, - textureOptions: null, - - // Setup the parent after - parent: null, - }); - return node; - } -} diff --git a/src/render-drivers/threadx/worker/ThreadXRendererTextNode.ts b/src/render-drivers/threadx/worker/ThreadXRendererTextNode.ts deleted file mode 100644 index dc12075b..00000000 --- a/src/render-drivers/threadx/worker/ThreadXRendererTextNode.ts +++ /dev/null @@ -1,151 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CoreTextNode } from '../../../core/CoreTextNode.js'; -import type { Stage } from '../../../core/Stage.js'; -import type { TrProps } from '../../../core/text-rendering/renderers/TextRenderer.js'; -import type { NodeStructWritableProps } from '../NodeStruct.js'; -import type { - TextNodeStruct, - TextNodeStructWritableProps, -} from '../TextNodeStruct.js'; -import { ThreadXRendererNode } from './ThreadXRendererNode.js'; - -export class ThreadXRendererTextNode extends ThreadXRendererNode { - declare coreNode: CoreTextNode; - declare z$__type__Props: TextNodeStructWritableProps & - ThreadXRendererNode['z$__type__Props']; - - constructor(stage: Stage, sharedNodeStruct: TextNodeStruct) { - super( - stage, - sharedNodeStruct, - new CoreTextNode(stage, { - id: sharedNodeStruct.id, - // It doesn't matter what these are initially. They will be reset - // appropriately via the onPropertyChange() calls below and in the - // ThreadXRendererNode constructor. - x: sharedNodeStruct.x, - y: sharedNodeStruct.y, - width: sharedNodeStruct.width, - height: sharedNodeStruct.height, - alpha: sharedNodeStruct.alpha, - autosize: sharedNodeStruct.autosize, - clipping: sharedNodeStruct.clipping, - color: sharedNodeStruct.color, - colorTop: sharedNodeStruct.colorTop, - colorBottom: sharedNodeStruct.colorBottom, - colorLeft: sharedNodeStruct.colorLeft, - colorRight: sharedNodeStruct.colorRight, - colorTl: sharedNodeStruct.colorTl, - colorTr: sharedNodeStruct.colorTr, - colorBl: sharedNodeStruct.colorBl, - colorBr: sharedNodeStruct.colorBr, - zIndex: sharedNodeStruct.zIndex, - zIndexLocked: sharedNodeStruct.zIndexLocked, - mount: sharedNodeStruct.mount, - mountX: sharedNodeStruct.mountX, - mountY: sharedNodeStruct.mountY, - pivot: sharedNodeStruct.pivot, - pivotX: sharedNodeStruct.pivotX, - pivotY: sharedNodeStruct.pivotY, - scaleX: sharedNodeStruct.scaleX, - scaleY: sharedNodeStruct.scaleY, - rotation: sharedNodeStruct.rotation, - rtt: sharedNodeStruct.rtt, - - // These are passed in via message handlers - shader: null, - shaderProps: null, - texture: null, - textureOptions: null, - - // Setup the parent after - parent: null, - - // Text properties - text: sharedNodeStruct.text, - textRendererOverride: sharedNodeStruct.textRendererOverride, - fontSize: sharedNodeStruct.fontSize, - fontFamily: sharedNodeStruct.fontFamily, - fontWeight: sharedNodeStruct.fontWeight, - fontStretch: sharedNodeStruct.fontStretch, - fontStyle: sharedNodeStruct.fontStyle, - lineHeight: sharedNodeStruct.lineHeight, - maxLines: sharedNodeStruct.maxLines, - textBaseline: sharedNodeStruct.textBaseline, - verticalAlign: sharedNodeStruct.verticalAlign, - overflowSuffix: sharedNodeStruct.overflowSuffix, - contain: sharedNodeStruct.contain, - letterSpacing: sharedNodeStruct.letterSpacing, - offsetY: sharedNodeStruct.offsetY, - textAlign: sharedNodeStruct.textAlign, - scrollable: sharedNodeStruct.scrollable, - scrollY: sharedNodeStruct.scrollY, - debug: {}, - }), - { - text: sharedNodeStruct.text, - textRendererOverride: sharedNodeStruct.textRendererOverride, - fontSize: sharedNodeStruct.fontSize, - fontFamily: sharedNodeStruct.fontFamily, - fontWeight: sharedNodeStruct.fontWeight, - fontStretch: sharedNodeStruct.fontStretch, - fontStyle: sharedNodeStruct.fontStyle, - lineHeight: sharedNodeStruct.lineHeight, - maxLines: sharedNodeStruct.maxLines, - textBaseline: sharedNodeStruct.textBaseline, - verticalAlign: sharedNodeStruct.verticalAlign, - overflowSuffix: sharedNodeStruct.overflowSuffix, - contain: sharedNodeStruct.contain, - letterSpacing: sharedNodeStruct.letterSpacing, - offsetY: sharedNodeStruct.offsetY, - textAlign: sharedNodeStruct.textAlign, - scrollable: sharedNodeStruct.scrollable, - scrollY: sharedNodeStruct.scrollY, - } satisfies Omit< - TextNodeStructWritableProps, - keyof NodeStructWritableProps | 'debug' - >, - ); - // Forward on CoreNode events - this.on('debug', (target: ThreadXRendererNode, debug: TrProps['debug']) => { - this.coreNode.debug = debug; - }); - } - - declare text: TextNodeStructWritableProps['text']; - declare textRendererOverride: TextNodeStructWritableProps['textRendererOverride']; - declare fontSize: TextNodeStructWritableProps['fontSize']; - declare fontFamily: TextNodeStructWritableProps['fontFamily']; - declare fontWeight: TextNodeStructWritableProps['fontWeight']; - declare fontStretch: TextNodeStructWritableProps['fontStretch']; - declare fontStyle: TextNodeStructWritableProps['fontStyle']; - declare lineHeight: TextNodeStructWritableProps['lineHeight']; - declare maxLines: TextNodeStructWritableProps['maxLines']; - declare textBaseline: TextNodeStructWritableProps['textBaseline']; - declare verticalAlign: TextNodeStructWritableProps['verticalAlign']; - declare overflowSuffix: TextNodeStructWritableProps['overflowSuffix']; - declare contain: TextNodeStructWritableProps['contain']; - declare letterSpacing: TextNodeStructWritableProps['letterSpacing']; - declare offsetY: TextNodeStructWritableProps['offsetY']; - declare textAlign: TextNodeStructWritableProps['textAlign']; - declare scrollable: TextNodeStructWritableProps['scrollable']; - declare scrollY: TextNodeStructWritableProps['scrollY']; -} diff --git a/src/render-drivers/threadx/worker/renderer.ts b/src/render-drivers/threadx/worker/renderer.ts deleted file mode 100644 index 8c97c860..00000000 --- a/src/render-drivers/threadx/worker/renderer.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ThreadX, BufferStruct } from '@lightningjs/threadx'; -import { NodeStruct, type NodeStructWritableProps } from '../NodeStruct.js'; -import { ThreadXRendererNode } from './ThreadXRendererNode.js'; -import { Stage, type StageFpsUpdateHandler } from '../../../core/Stage.js'; -import { assertTruthy } from '../../../utils.js'; -import { - isThreadXRendererMessage, - type ThreadXRendererFpsUpdateMessage, - type ThreadXRendererMessage, -} from '../ThreadXRendererMessage.js'; -import { TextNodeStruct } from '../TextNodeStruct.js'; -import { ThreadXRendererTextNode } from './ThreadXRendererTextNode.js'; -import { loadCoreExtension } from '../../utils.js'; - -let canvas: OffscreenCanvas | null = null; -let stage: Stage | null = null; -let rootNode: ThreadXRendererNode | null = null; -const threadx = ThreadX.init({ - workerId: 2, - workerName: 'renderer', - sharedObjectFactory(buffer) { - const typeId = BufferStruct.extractTypeId(buffer); - if (typeId === NodeStruct.typeId) { - const nodeStruct = new NodeStruct(buffer); - nodeStruct.parentId = nodeStruct.parentId || 0; - const node = nodeStruct.lock(() => { - assertTruthy(stage); - return new ThreadXRendererNode(stage, nodeStruct); - }); - return node; - } else if (typeId === TextNodeStruct.typeId) { - const nodeStruct = new TextNodeStruct(buffer); - nodeStruct.parentId = nodeStruct.parentId || 0; - const node = nodeStruct.lock(() => { - assertTruthy(stage); - return new ThreadXRendererTextNode(stage, nodeStruct); - }); - return node; - } - return null; - }, - async onMessage(message: ThreadXRendererMessage) { - if (isThreadXRendererMessage('init', message)) { - canvas = message.canvas; - const nodeStruct = new NodeStruct(); - stage = new Stage({ - rootId: nodeStruct.id, - appWidth: message.appWidth, - appHeight: message.appHeight, - txMemByteThreshold: message.txMemByteThreshold, - boundsMargin: message.boundsMargin, - deviceLogicalPixelRatio: message.deviceLogicalPixelRatio, - devicePhysicalPixelRatio: message.devicePhysicalPixelRatio, - clearColor: message.clearColor, - canvas, - fpsUpdateInterval: message.fpsUpdateInterval, - enableContextSpy: message.enableContextSpy, - numImageWorkers: message.numImageWorkers, - renderMode: 'webgl', - debug: { - monitorTextureCache: false, - }, - }); - - const coreRootNode = stage.root; - - // We must initialize the root NodeStruct with the same properties from - // the CoreNode that the Stage created. - Object.assign(nodeStruct, { - x: coreRootNode.x, - y: coreRootNode.y, - width: coreRootNode.width, - height: coreRootNode.height, - alpha: coreRootNode.alpha, - autosize: coreRootNode.autosize, - clipping: coreRootNode.clipping, - color: coreRootNode.color, - colorTop: coreRootNode.colorTop, - colorRight: coreRootNode.colorRight, - colorBottom: coreRootNode.colorBottom, - colorLeft: coreRootNode.colorLeft, - colorTl: coreRootNode.colorTl, - colorTr: coreRootNode.colorTr, - colorBr: coreRootNode.colorBr, - colorBl: coreRootNode.colorBl, - parentId: coreRootNode.parent?.id ?? 0, - zIndex: coreRootNode.zIndex, - zIndexLocked: coreRootNode.zIndexLocked, - scaleX: coreRootNode.scaleX, - scaleY: coreRootNode.scaleY, - mount: coreRootNode.mount, - mountX: coreRootNode.mountX, - mountY: coreRootNode.mountY, - pivot: coreRootNode.pivot, - pivotX: coreRootNode.pivotX, - pivotY: coreRootNode.pivotY, - rotation: coreRootNode.rotation, - rtt: coreRootNode.rtt, - } satisfies NodeStructWritableProps); - - // Share the root node that was created by the Stage with the main worker. - rootNode = new ThreadXRendererNode(stage, nodeStruct, coreRootNode); - await threadx.shareObjects('parent', [rootNode]); - - // Load the Core Extension Module if one was specified. - if (message.coreExtensionModule) { - await loadCoreExtension(message.coreExtensionModule, stage); - } - - // Forward FPS updates to the main worker. - stage.on('fpsUpdate', ((stage, fpsData) => { - threadx.sendMessage('parent', { - type: 'fpsUpdate', - fpsData: fpsData, - } satisfies ThreadXRendererFpsUpdateMessage); - }) satisfies StageFpsUpdateHandler); - - // Return its ID so the main worker can retrieve it from the shared object - // store. - return rootNode.id; - } else if (isThreadXRendererMessage('releaseTexture', message)) { - assertTruthy(stage); - const txManager = stage.txManager; - assertTruthy(txManager); - txManager.removeTextureIdFromCache(message.textureDescId); - } - }, - onObjectShared(object) { - // TBD - }, - onBeforeObjectForgotten(object) { - if (object instanceof ThreadXRendererNode) { - object.parent = null; - object.destroy(); - } - }, -}); diff --git a/src/render-drivers/utils.ts b/src/render-drivers/utils.ts deleted file mode 100644 index 907619d5..00000000 --- a/src/render-drivers/utils.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { CoreExtension } from '../../exports/core-api.js'; -import type { Stage } from '../core/Stage.js'; -import type { CustomDataMap } from '../main-api/INode.js'; - -/** - * Type guard that checks if a Class extends CoreExtension. - * - * @param Class - * @returns - */ -export function classExtendsCoreExtension( - Class: unknown, -): Class is new () => CoreExtension { - return (Class as typeof CoreExtension).prototype instanceof CoreExtension; -} - -export async function loadCoreExtension( - coreExtensionModule: string, - stage: Stage, -) { - let module: { - default?: unknown; - }; - try { - console.log('Loading core extension', coreExtensionModule); - module = (await import( - coreExtensionModule /* @vite-ignore */ - )) as typeof module; - } catch (e: unknown) { - console.error( - `The core extension module at '${coreExtensionModule}' could not be loaded.`, - ); - console.error(e); - return; - } - if (!module.default) { - console.error( - `The core extension module at '${coreExtensionModule}' does not have a default export.`, - ); - return; - } - const ExtensionClass = module.default; - if (classExtendsCoreExtension(ExtensionClass)) { - const coreExtension = new ExtensionClass(); - try { - await coreExtension.run(stage); - } catch (e: unknown) { - console.error( - `The core extension at '${coreExtensionModule}' threw an error.`, - ); - console.error(e); - } - } else { - console.error( - `The core extension at '${coreExtensionModule}' does not extend CoreExtension.`, - ); - } -} - -export function santizeCustomDataMap(d: CustomDataMap): CustomDataMap { - const validTypes = { - boolean: true, - string: true, - number: true, - undefined: true, - }; - - const keys = Object.keys(d); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (!key) { - continue; - } - - const value = d[key]; - const valueType = typeof value; - - // Typescript doesn't understand the above const valueType ¯\_(ツ)_/¯ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - if (valueType === 'string' && value.length > 2048) { - console.warn( - `Custom Data value for ${key} is too long, it will be truncated to 2048 characters`, - ); - - // same here, see above comment, this can only be a string at this point - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - d[key] = value.substring(0, 2048); - } - - if (!validTypes[valueType as keyof typeof validTypes]) { - console.warn( - `Custom Data value for ${key} is not a boolean, string, or number, it will be ignored`, - ); - delete d[key]; - } - } - - return d; -} diff --git a/src/utils.ts b/src/utils.ts index 29f12369..b72067d5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -237,3 +237,11 @@ export function getImageAspectRatio(width: number, height: number): number { export function isProductionEnvironment(): boolean { return import.meta.env && import.meta.env.PROD; } + +/** + * Returns a new unique ID + */ +let nextId = 1; +export function getNewId(): number { + return nextId++; +} diff --git a/tsconfig.cfg.json b/tsconfig.cfg.json index 2fa4fccf..26bdd6f6 100644 --- a/tsconfig.cfg.json +++ b/tsconfig.cfg.json @@ -3,8 +3,8 @@ "outDir": "dist-cfg", "types": ["@types/node"], "target": "ESNext", - "module": "ESNext", - "moduleResolution": "node", + "module": "Node16", + "moduleResolution": "Node16", "composite": true, "resolveJsonModule": true, "allowJs": true, diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json index ca623b36..2a671a55 100644 --- a/tsconfig.vitest.json +++ b/tsconfig.vitest.json @@ -5,7 +5,7 @@ "types": [], "composite": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "Bundler", "resolveJsonModule": true, "paths": { "test/*": ["./test/*"] diff --git a/visual-regression/certified-snapshots/chromium-ci/destroy-1.png b/visual-regression/certified-snapshots/chromium-ci/destroy-1.png index 3e6d3752..addcea1e 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/destroy-1.png and b/visual-regression/certified-snapshots/chromium-ci/destroy-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/rtt-dimension-1.png b/visual-regression/certified-snapshots/chromium-ci/rtt-dimension-1.png index 47fac0c7..305381b9 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/rtt-dimension-1.png and b/visual-regression/certified-snapshots/chromium-ci/rtt-dimension-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation1-1.png b/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation1-1.png new file mode 100644 index 00000000..bd072f67 Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation1-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation2-1.png b/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation2-1.png new file mode 100644 index 00000000..eef83929 Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/shader-animation_animation2-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/shader-animation_startup-1.png b/visual-regression/certified-snapshots/chromium-ci/shader-animation_startup-1.png new file mode 100644 index 00000000..53e395be Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/shader-animation_startup-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-1.png b/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-1.png index 76b457df..7e1e1121 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-1.png and b/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-2.png b/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-2.png index 9e88df3b..03398314 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-2.png and b/visual-regression/certified-snapshots/chromium-ci/text-canvas-font-no-metrics-2.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/texture-factory-1.png b/visual-regression/certified-snapshots/chromium-ci/texture-factory-1.png index 880fdba6..2c009360 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/texture-factory-1.png and b/visual-regression/certified-snapshots/chromium-ci/texture-factory-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/textures-1.png b/visual-regression/certified-snapshots/chromium-ci/textures-1.png index 2ec701e5..a67de447 100644 Binary files a/visual-regression/certified-snapshots/chromium-ci/textures-1.png and b/visual-regression/certified-snapshots/chromium-ci/textures-1.png differ diff --git a/visual-regression/src/index.ts b/visual-regression/src/index.ts index b4668d05..ded2d7a5 100644 --- a/visual-regression/src/index.ts +++ b/visual-regression/src/index.ts @@ -292,11 +292,6 @@ async function runTest(browserType: 'chromium') { page.on('console', (msg) => console.log(`console: ${msg.text()}`)); } - // Go to the examples page - await page.goto( - `http://localhost:${argv.port}/?automation=true&test=${argv.filter}`, - ); - /** * Keeps track of the latest snapshot index for each test */ @@ -504,6 +499,11 @@ async function runTest(browserType: 'chromium') { } }); + // Go to the examples page + await page.goto( + `http://localhost:${argv.port}/?automation=true&test=${argv.filter}`, + ); + return donePromise; } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..1cf1bb08 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + all: true, + include: ['src/**/*.ts'], + }, + }, +});