diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc48702..829fdf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,11 @@ jobs: - name: Typecheck files run: yarn typecheck + - name: Test + run: yarn test + + - name: Test example + run: yarn example test build-library: runs-on: ubuntu-latest @@ -104,6 +109,9 @@ jobs: run: | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + - name: Android unit tests + run: cd example/android && ./gradlew :react-native-splatkit:testDebugUnitTest --console=plain + build-ios: runs-on: macos-latest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..c110bf3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,62 @@ +name: Publish +on: + push: + tags: + - 'v*' + +permissions: + contents: read + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Node for npm + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .nvmrc + registry-url: https://registry.npmjs.org + + - name: Install JDK + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: 'zulu' + java-version: '17' + + - name: Check + run: yarn lint && yarn typecheck && yarn test && yarn example test + + - name: Android unit tests + run: cd example/android && ./gradlew :react-native-splatkit:testDebugUnitTest --console=plain + + - name: Build + run: yarn prepare + + - name: Version matches tag + run: | + TAG="${GITHUB_REF_NAME#v}" + VERSION="$(node -p "require('./package.json').version")" + test "$TAG" = "$VERSION" || { echo "tag $TAG does not match package.json $VERSION"; exit 1; } + + - name: Dist tag from the version + run: | + case "$GITHUB_REF_NAME" in + *-alpha*) echo "DIST_TAG=alpha" >> "$GITHUB_ENV" ;; + *-beta*) echo "DIST_TAG=beta" >> "$GITHUB_ENV" ;; + *-rc*) echo "DIST_TAG=next" >> "$GITHUB_ENV" ;; + *) echo "DIST_TAG=latest" >> "$GITHUB_ENV" ;; + esac + + # Trusted publishing (OIDC, no secret) when it is configured on npmjs.com; + # otherwise the NPM_TOKEN secret. Either way the tarball carries provenance. + - name: Publish + run: npm publish --provenance --access public --tag "$DIST_TAG" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 67f3212..4d08ed3 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ example/ios/Pods # Ruby example/vendor/ +example/Gemfile.lock # node.js # diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 45d257b..f646209 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. +juanieltupa@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57e9cd4..a3df2e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,11 +83,12 @@ The `package.json` file contains various scripts for common tasks: - `yarn`: setup project by installing dependencies. - `yarn typecheck`: type-check files with TypeScript. - - `yarn lint`: lint files with [ESLint](https://eslint.org/). - - `yarn example start`: start the Metro server for the example app. +- `yarn lint`: lint files with [ESLint](https://eslint.org/). +- `yarn test`: run the Jest tests. +- `yarn example start`: start the Metro server for the example app. - `yarn example android`: run the example app on Android. - `yarn example ios`: run the example app on iOS. - + ### Sending a pull request > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). @@ -97,5 +98,5 @@ When you're sending a pull request: - Prefer small pull requests focused on one change. - Verify that linters and tests are passing. - Review the documentation to make sure it looks good. -- Follow the pull request template when opening a pull request. +- One sentence per line in Markdown, plain dashes. - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. diff --git a/README.md b/README.md index 9252f34..7fe79f3 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ import { SplatView } from 'react-native-splatkit'; console.log(e.nativeEvent.gpu)} onWorldReady={(e) => console.log(e.nativeEvent.splatCount, 'splats')} @@ -47,7 +47,7 @@ The bytes never cross the bridge. JavaScript hands over a location and the native side reads it on a background thread, because a world is tens to hundreds of megabytes and serialising that would stall the app for as long as it took. `file://`, `content://`, `asset://` for a file in the app assets, `http://`, `https://`, or an absolute path. -A file on disk goes to the engine as a path and is mapped, not copied through the Java heap; the other schemes are read to bytes first. +A file on disk goes to the engine as a path and is mapped, not copied through the Java heap; the other schemes are streamed to the app's cache directory once and mapped from there, with `onLoadProgress` along the way. ### Props @@ -55,10 +55,10 @@ A file on disk goes to the engine as a path and is mapped, not copied through th |---|---| | `source` | The world. SPZ versions 2 to 4; the format is detected from the bytes. | | `collider` | A GLB mesh. Switches the camera from flying to walking. | -| `quality` | A preset name, `low`, `medium`, `high` (the default) or `ultra`, or a preset plus overrides: `{ preset: 'medium', renderScale: 0.8 }`. The overrides are `renderScale` (0.1 to 2, above 1 supersamples), `shDegree` (0 to 3, the harmonics degree drawn), `splatBudget` (0 draws all), `cullMarginDegrees` and `linearBlending`. The reason behind each preset and its frame times are in the [engine's README](https://github.com/Xget7/splatkit-android/blob/main/packages/splatkit-android/README.md). | +| `quality` | A preset name, `low`, `medium`, `high` (the default) or `ultra`, or a preset plus overrides: `{ preset: 'medium', renderScale: 0.8 }`. The overrides are `renderScale` (0.1 to 2, above 1 supersamples), `shDegree` (0 to 3, rounded, the harmonics degree drawn), `splatBudget` (0 draws all), `cullMarginDegrees` (0 to 90) and `linearBlending`. Out of range values are clamped with a warning in development; an unknown preset falls back to `high`. The reason behind each preset and its frame times are in the [engine's README](https://github.com/Xget7/splatkit-android/blob/main/packages/splatkit-android/README.md). | | `cameraPose` | `{ x, y, z, yaw?, pitch? }`, meters and radians. Applied when it changes and again when the world and the collider become ready, so it can be set before the world loads. When walking the camera settles on the floor under the point. | | `motionEnabled` | The gyroscope drives the look direction. | -| `lookSensitivity`, `walkSensitivity` | Gesture tuning. | +| `lookSensitivity`, `walkSensitivity` | Gesture tuning. Radians per pixel for one finger looking (default 0.004) and meters per pixel for two finger walking (default 0.01). | | `statsInterval` | Milliseconds between `onStats`. 0, the default, turns the event off. | ### Events @@ -66,12 +66,15 @@ A file on disk goes to the engine as a path and is mapped, not copied through th `onEngineReady` fires once with `{ available, gpu }`. When `available` is false the device could not start the renderer and the view stays blank; every other call is a no-op. +`onLoadProgress` gives `{ kind, bytes, total }` at most every 100 ms while a source that is not a local file is being copied; `kind` is `world` or `collider` and `total` is -1 when the server did not say. `onWorldReady` gives `{ splatCount }`, `onWorldFailed` and `onColliderFailed` give `{ message }`, `onColliderReady` takes no payload, and `onStats` gives `{ fps, frameMs, gpuMs, sortMs, splatCount, pose }`, where `pose` is the camera as of the last frame in the shape of `cameraPose`. Read it to save a viewpoint and hand it back later. ### Imperative ```tsx +import { SplatView, type SplatViewHandle } from 'react-native-splatkit'; + const splat = useRef(null); splat.current?.setWalkVelocity(forward, right); // meters per second, for a joystick @@ -79,11 +82,64 @@ splat.current?.setCameraPose({ x: 0, y: 1.5, z: 0 }); // teleport; yaw and pi splat.current?.startBenchmark(10); // a reproducible turn, timings in logcat ``` -## Requirements +## Requirements and setup + +React Native 0.85 or newer with the New Architecture (the default since 0.76); there is no interop layer support. +Android 10 (API 29) and a Vulkan 1.1 device. + +In `android/build.gradle` of the app set the floor the engine needs: + +```groovy +ext { + minSdkVersion = 29 +} +``` + +The library pins 29 itself, so a lower app floor fails at build time with this package's name in the message rather than at runtime. + +The engine ships `arm64-v8a` only. +An app that builds every ABI still installs on an x86_64 emulator and then dies on the first frame, so develop on a physical arm64 device, or keep the emulator from installing it at all with `reactNativeArchitectures=arm64-v8a` in `android/gradle.properties`. + +### Expo + +Works in a development build, not in Expo Go. +Raise the floor with `expo-build-properties`: + +```json +["expo-build-properties", { "android": { "minSdkVersion": 29 } }] +``` + +### Remote sources + +A release build refuses plain `http://` from Android 9 on, and the refusal arrives as `onWorldFailed` with "Cleartext HTTP traffic ... not permitted". +Serve worlds over `https://`, or allow cleartext in the host app's manifest or network security config for development. + +### Retrying, unloading, caching + +React Native resends a prop only when it changes, so after `onWorldFailed` a retry needs a new `uri` (a query string will do) or a new `key` on the view. +The engine has no unload call yet; setting `source` to `undefined` leaves the current world in place. +Sources that are not local files are copied once to the app's cache directory, keyed by URI, and mapped from there; a changed file behind the same URI is not noticed, so change the URI or clear the app cache. +The cache is capped at 512 MB and evicts the least recently used world first, so a handful of worlds stay and a season's worth does not. + +### Children + +`SplatView` does not lay out React children. +Put a HUD or a joystick in a sibling view, as the example does. -New architecture only. -Android 10 (API 29) and a Vulkan 1.1 device, `arm64-v8a` only: the engine ships that ABI alone, so an x86_64 emulator installs and then dies on the first frame. -Develop on a physical arm64 device. +## Performance + +Measured on a Xiaomi Mi 9 (Adreno 640), the 500k splat World Labs kitchen with its collider, preset `medium`, release builds, camera at the origin, same session, phone cooled between runs. +The engine's own benchmark reports the numbers (one turn over 10 s); the binding adds nothing to the frame. + +| Host | GPU ms p50 | frame ms p50 | fps | +|---|---|---|---| +| Engine dev app | 12.8 | 16.7 | 59.6 | +| This package, example app | 12.8 | 16.7 | 59.6 | + +`statsInterval={16}`, one event per frame, measured the same frame time as `0`, so a HUD can run at any rate. +A remote world is streamed to disk and mapped, so loading a 51 MB file (3.6 M splats) kept the Java heap under 8 MB; read into memory it peaked at 58 MB. +Two views on one screen both render; expect the frame rate to split between them. +The engine's numbers per preset and per scene are in [docs/BENCHMARKS.md](https://github.com/Xget7/splatkit-android/blob/main/docs/BENCHMARKS.md). ## iOS @@ -109,6 +165,10 @@ Reading `/sdcard/Download` instead means asking for `READ_EXTERNAL_STORAGE`, whi Everything from the engine logs under the tag `SplatKit`. MIUI hides application logs until `adb shell setprop persist.log.tag.SplatKit V`. +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the development loop, the checks that run on a pull request and the conventions. + ## License MIT. diff --git a/Splatkit.podspec b/Splatkit.podspec index 5a19908..bd47bdc 100644 --- a/Splatkit.podspec +++ b/Splatkit.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.authors = package["author"] s.platforms = { :ios => min_ios_version_supported } - s.source = { :git => "https://github.com/Xget7/react-native-splatkit.git", :tag => "#{s.version}" } + s.source = { :git => "https://github.com/Xget7/react-native-splatkit.git", :tag => "v#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,swift,cpp}" s.private_header_files = "ios/**/*.h" diff --git a/android/build.gradle b/android/build.gradle index 52802ac..06f81a8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,9 +1,6 @@ buildscript { ext.Splatkit = [ kotlinVersion: "2.1.21", - // The engine needs Android 10 and Vulkan 1.1; there is no fallback renderer - // below that, so the floor is the engine's floor and not React Native's. - minSdkVersion: 29, compileSdkVersion: 36, // The one coupling between this package and the engine. Nothing else about // the renderer is visible from here. @@ -42,7 +39,11 @@ android { compileSdkVersion getExtOrDefault("compileSdkVersion") defaultConfig { - minSdkVersion getExtOrDefault("minSdkVersion") + // The engine needs Android 10 and Vulkan 1.1; there is no fallback renderer + // below that. This is pinned rather than read from the app so that an app + // on a lower floor fails here, with this package's name in the message, + // instead of deep inside the engine's manifest merge. + minSdkVersion 29 } compileOptions { @@ -53,6 +54,11 @@ android { kotlinOptions { jvmTarget = "17" } + + testOptions { + // android.util.Log in the fetcher; the unit tests run on a plain JVM. + unitTests.returnDefaultValues = true + } } repositories { @@ -66,4 +72,6 @@ dependencies { // no shaders and no native code of its own; it only puts the engine's view in // a React Native tree. api "io.github.xget7:splatkit-android:${getExtOrDefault('splatkitVersion')}" + + testImplementation "junit:junit:4.13.2" } diff --git a/android/src/main/java/com/splatkit/reactnative/QualityMapper.kt b/android/src/main/java/com/splatkit/reactnative/QualityMapper.kt new file mode 100644 index 0000000..a6f9d1b --- /dev/null +++ b/android/src/main/java/com/splatkit/reactnative/QualityMapper.kt @@ -0,0 +1,79 @@ +package com.splatkit.reactnative + +import com.facebook.react.bridge.ReadableMap +import com.splatkit.RenderQuality + +/** + * The `quality` prop as the JavaScript wrapper sends it: a preset name and one + * value per override, where -1 means "keep the preset's". Absent keys are + * treated the same way. + * + * Never throws. A prop is not a place to crash the app from: an unknown preset, + * a value of the wrong type or a negative number other than -1 is reported + * through [warn] and the preset's value is used instead. + */ +internal object QualityMapper { + const val UNSET = -1 + + fun fromMap(map: ReadableMap?, warn: (String) -> Unit): RenderQuality { + if (map == null) return RenderQuality.HIGH + val name = map.stringOrNull("preset", warn) + val preset = name?.let(RenderQuality::named) ?: run { + if (name != null) warn("unknown quality preset '$name'; using high") + RenderQuality.HIGH + } + return preset.copy( + renderScale = map.floatOr("renderScale", preset.renderScale, warn), + shDegree = map.intOr("shDegree", preset.shDegree, warn), + splatBudget = map.intOr("splatBudget", preset.splatBudget, warn), + cullMarginDegrees = map.floatOr("cullMarginDegrees", preset.cullMarginDegrees, warn), + linearBlending = when (map.intOr("linearBlending", UNSET, warn)) { + UNSET -> preset.linearBlending + 0 -> false + else -> true + }, + ) + } + + private fun ReadableMap.stringOrNull(key: String, warn: (String) -> Unit): String? { + if (!hasKey(key) || isNull(key)) return null + return try { + getString(key) + } catch (e: RuntimeException) { + warn("quality.$key is not a string; ignoring it") + null + } + } + + private fun ReadableMap.floatOr(key: String, fallback: Float, warn: (String) -> Unit): Float { + if (!hasKey(key) || isNull(key)) return fallback + val value = try { + getDouble(key) + } catch (e: RuntimeException) { + warn("quality.$key is not a number; ignoring it") + return fallback + } + if (value == UNSET.toDouble()) return fallback + if (value < 0) { + warn("quality.$key $value is negative; ignoring it") + return fallback + } + return value.toFloat() + } + + private fun ReadableMap.intOr(key: String, fallback: Int, warn: (String) -> Unit): Int { + if (!hasKey(key) || isNull(key)) return fallback + val value = try { + getInt(key) + } catch (e: RuntimeException) { + warn("quality.$key is not a number; ignoring it") + return fallback + } + if (value == UNSET) return fallback + if (value < 0) { + warn("quality.$key $value is negative; ignoring it") + return fallback + } + return value + } +} diff --git a/android/src/main/java/com/splatkit/reactnative/SourceFetcher.kt b/android/src/main/java/com/splatkit/reactnative/SourceFetcher.kt new file mode 100644 index 0000000..b8aec7d --- /dev/null +++ b/android/src/main/java/com/splatkit/reactnative/SourceFetcher.kt @@ -0,0 +1,191 @@ +package com.splatkit.reactnative + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.util.Log +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.net.HttpURLConnection +import java.net.URL +import java.security.MessageDigest +import java.util.concurrent.CancellationException + +/** A stream plus its declared length, -1 when the source does not say. */ +internal typealias Opened = Pair + +/** + * Turns a source that is not already a file on disk into one, in the app's + * cache directory, so the engine can map it instead of the binding holding it + * on the Java heap. A world is tens to hundreds of megabytes; a `ByteArray` + * that size is an `OutOfMemoryError` waiting for a phone with less RAM. + * + * The cache is keyed by the URI. A changed file behind the same URI is not + * noticed; the README tells apps to bust it with a query string. + * + * Writes go to a `.part` file unique to this fetch and are renamed at the end, + * after the byte count matched what the source declared, so neither a crash, + * a cancellation nor a connection cut short leaves a file that looks complete. + * + * `http(s)` is handled here with the JDK alone; `asset://` and `content://` + * come through [platform] so the class can be tested on a plain JVM. + */ +internal class SourceFetcher( + cacheRoot: File, + private val cacheCapBytes: Long = CACHE_CAP_BYTES, + private val platform: (String) -> Opened?, +) { + constructor(context: Context) : this(context.cacheDir, platform = AndroidSources(context)) + + private val dir = File(cacheRoot, "splatkit") + + @Volatile private var connection: HttpURLConnection? = null + @Volatile private var closed = false + + init { + if (!dir.mkdirs() && !dir.isDirectory) Log.e(TAG, "cannot create the cache directory at $dir") + // Another view of this app may be writing its own .part right now, so + // only what nobody could still be writing is swept. + val stale = System.currentTimeMillis() - STALE_PART_MS + dir.listFiles { f -> f.name.endsWith(".part") && f.lastModified() < stale }?.forEach { it.delete() } + trim(cacheCapBytes) + } + + /** + * Keeps the finished files under the cap, oldest use first. A hit touches its + * file, so a world in use stays while one from last month goes. + */ + private fun trim(cap: Long) { + val finished = dir.listFiles { f -> f.isFile && !f.name.endsWith(".part") } ?: return + var kept = 0L + finished.sortedByDescending { it.lastModified() }.forEach { f -> + kept += f.length() + if (kept > cap && !f.delete()) Log.w(TAG, "could not evict ${f.name}") + } + } + + /** Called from any thread; makes a blocked network read fail promptly. */ + fun disconnect() { + closed = true + connection?.disconnect() + } + + @Throws(IOException::class) + fun fetch(uri: String, cancelled: () -> Boolean, progress: (Long, Long) -> Unit): File { + val target = File(dir, "${sha1(uri)}${extensionOf(uri)}") + if (target.isFile && target.length() > 0) { + target.setLastModified(System.currentTimeMillis()) + progress(target.length(), target.length()) + return target + } + val (stream, total) = open(uri) + val part = File.createTempFile(target.name, ".part", dir) + try { + var copied = 0L + stream.use { input -> + FileOutputStream(part).use { output -> + val buffer = ByteArray(256 * 1024) + while (true) { + if (closed || cancelled() || Thread.currentThread().isInterrupted) { + throw CancellationException("cancelled: $uri") + } + val n = input.read(buffer) + if (n < 0) break + output.write(buffer, 0, n) + copied += n + progress(copied, total) + } + } + } + if (total >= 0 && copied != total) { + throw IOException("truncated: got $copied of $total bytes for $uri") + } + if (!part.renameTo(target)) { + throw IOException("could not move the downloaded file into place at $target for $uri") + } + progress(copied, if (total >= 0) total else copied) + return target + } catch (e: Throwable) { + if (!part.delete() && part.exists()) Log.w(TAG, "could not delete ${part.name}") + throw e + } finally { + connection = null + } + } + + private fun open(uri: String): Opened = when { + uri.startsWith("http://") || uri.startsWith("https://") -> openHttp(uri) + else -> platform(uri) ?: throw IOException("unsupported source: $uri") + } + + private fun openHttp(uri: String): Opened { + val c = (URL(uri).openConnection() as HttpURLConnection).apply { + connectTimeout = 15_000 + readTimeout = 30_000 + instanceFollowRedirects = true + } + connection = c + if (closed) { + c.disconnect() + throw CancellationException("cancelled: $uri") + } + val code = try { + c.responseCode + } catch (e: IOException) { + connection = null + throw e + } + if (code !in 200..299) { + c.disconnect() + connection = null + throw IOException("HTTP $code for $uri") + } + // A transparently decompressed body reports the compressed length, which + // the byte count can never match; treat it as unknown. + val total = if (c.contentEncoding.isNullOrEmpty()) c.contentLengthLong else -1L + return c.inputStream to total + } + + companion object { + private const val TAG = "SplatKit" + private const val STALE_PART_MS = 60 * 60 * 1000L + /** Worlds run to a hundred MB or more; a handful is worth keeping, a season's worth is not. */ + private const val CACHE_CAP_BYTES = 512L * 1024 * 1024 + + /** The extension of the last path segment, before any query or fragment; empty when there is none. */ + fun extensionOf(uri: String): String { + val name = uri.substringBefore('#').substringBefore('?').substringAfterLast('/') + val dot = name.lastIndexOf('.') + return if (dot > 0) name.substring(dot) else "" + } + + fun sha1(text: String): String = + MessageDigest.getInstance("SHA-1").digest(text.toByteArray()) + .joinToString("") { "%02x".format(it) } + } +} + +/** The sources only Android can open: app assets and content providers. */ +internal class AndroidSources(private val context: Context) : (String) -> Opened? { + override fun invoke(uri: String): Opened? = when { + uri.startsWith("asset://") -> + context.assets.open(uri.removePrefix("asset://")) to -1L + + uri.startsWith("${ContentResolver.SCHEME_CONTENT}://") -> { + val parsed = Uri.parse(uri) + val stream = context.contentResolver.openInputStream(parsed) + ?: throw IOException("the content provider returned nothing for $uri") + val length = try { + context.contentResolver.openAssetFileDescriptor(parsed, "r")?.use { it.length } ?: -1L + } catch (e: Exception) { + Log.w("SplatKit", "no declared length for $uri", e) + -1L + } + stream to length + } + + else -> null + } +} diff --git a/android/src/main/java/com/splatkit/reactnative/SplatKitView.kt b/android/src/main/java/com/splatkit/reactnative/SplatKitView.kt index 1fd7f1c..4844283 100644 --- a/android/src/main/java/com/splatkit/reactnative/SplatKitView.kt +++ b/android/src/main/java/com/splatkit/reactnative/SplatKitView.kt @@ -1,9 +1,10 @@ package com.splatkit.reactnative -import android.content.ContentResolver import android.net.Uri import android.os.Handler import android.os.Looper +import android.os.SystemClock +import android.util.Log import android.widget.FrameLayout import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.LifecycleEventListener @@ -17,7 +18,7 @@ import com.splatkit.RenderQuality import com.splatkit.SplatStats import com.splatkit.SplatSurfaceView import java.io.File -import java.net.URL +import java.util.concurrent.CancellationException import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicLong @@ -41,6 +42,7 @@ class SplatKitView(private val reactContext: ThemedReactContext) : // File and network reads only. Decoding already runs on the engine's own // loader thread, so this stays free for the next source. private val io = Executors.newSingleThreadExecutor { Thread(it, "SplatKitRnIo") } + private val fetcher = SourceFetcher(reactContext) // A source that arrives while an older one is still being read must win, and // the older result must be dropped rather than replace it. The world and the @@ -56,8 +58,32 @@ class SplatKitView(private val reactContext: ThemedReactContext) : private var running = false private var statsIntervalMs = 0 - private var statsTicking = false + // The one runnable that may be queued; setting the interval or pausing removes it, + // so two cannot loop at once. + private val statsTick = object : Runnable { + override fun run() { + if (statsIntervalMs <= 0 || !running) return + surface.readStats(stats) + emit("topStats", Arguments.createMap().apply { + putDouble("fps", stats.fps.toDouble()) + putDouble("frameMs", stats.frameMillis.toDouble()) + putDouble("gpuMs", stats.gpuMillis.toDouble()) + putDouble("sortMs", stats.sortMillis.toDouble()) + putInt("splatCount", stats.splatCount) + val pose = surface.cameraPose + putMap("pose", Arguments.createMap().apply { + putDouble("x", pose.x.toDouble()) + putDouble("y", pose.y.toDouble()) + putDouble("z", pose.z.toDouble()) + putDouble("yaw", pose.yaw.toDouble()) + putDouble("pitch", pose.pitch.toDouble()) + }) + }) + main.postDelayed(this, statsIntervalMs.toLong()) + } + } private var announcedEngine = false + @Volatile private var released = false private var currentWorldUri: String? = null private var currentColliderUri: String? = null @@ -128,6 +154,8 @@ class SplatKitView(private val reactContext: ThemedReactContext) : if (shouldRun == running) return running = shouldRun if (shouldRun) surface.resume() else surface.pause() + // A paused engine has no new stats; the ticker rests with it. + syncStatsTicker() } // React Native does not lay out children of a view it does not manage, and a @@ -147,48 +175,67 @@ class SplatKitView(private val reactContext: ThemedReactContext) : if (uri == currentWorldUri) return currentWorldUri = uri worldReady = false - load(uri, worldGeneration, "topWorldFailed", surface::loadWorld, surface::loadWorld) + load(uri, "world", worldGeneration, "topWorldFailed", surface::loadWorld) } fun setCollider(uri: String?) { if (uri == currentColliderUri) return currentColliderUri = uri - load(uri, colliderGeneration, "topColliderFailed", surface::loadCollider, surface::loadCollider) + load(uri, "collider", colliderGeneration, "topColliderFailed", surface::loadCollider) } /** * A file on disk goes to the engine as a path, which maps it instead of - * copying it through the Java heap. Everything else is read to bytes here. - * Either way the hand over is posted, so it lands after the props of the - * same transaction (quality applies to worlds loaded after it is set). + * copying it through the Java heap. Everything else is streamed to a cache + * file first and then handed over the same way. Either way the hand over is + * posted, so it lands after the props of the same transaction (a budget + * applies to worlds loaded after it is set). */ private fun load( uri: String?, + kind: String, generations: AtomicLong, failureEvent: String, - handFile: (File) -> Unit, - handBytes: (ByteArray) -> Unit, + hand: (File) -> Unit, ) { - if (uri.isNullOrEmpty()) return + // Bumped before the empty check so that clearing the prop also withdraws + // a load still in flight. val generation = generations.incrementAndGet() + if (uri.isNullOrEmpty()) return + val stale = { generation != generations.get() } fileOf(uri)?.let { file -> - main.post { if (generation == generations.get()) handFile(file) } + main.post { if (!stale() && !released) hand(file) } return } io.execute { - val bytes = try { - readBytes(uri) + var lastProgressAt = 0L + val file = try { + fetcher.fetch(uri, stale) { bytes, total -> + val now = SystemClock.uptimeMillis() + if (now - lastProgressAt < 100 && bytes != total) return@fetch + lastProgressAt = now + main.post { + if (stale()) return@post + emit("topLoadProgress", Arguments.createMap().apply { + putString("kind", kind) + putDouble("bytes", bytes.toDouble()) + putDouble("total", total.toDouble()) + }) + } + } + } catch (e: CancellationException) { + return@execute } catch (e: Exception) { - if (generation != generations.get()) return@execute + if (stale() || released) return@execute + Log.w(TAG, "$kind failed: $uri", e) main.post { emit(failureEvent, Arguments.createMap().apply { - putString("message", "${e.javaClass.simpleName}: ${e.message} ($uri)") + putString("message", "${e.javaClass.simpleName}: ${e.message ?: "no detail"} ($uri)") }) } return@execute } - if (generation != generations.get()) return@execute - main.post { handBytes(bytes) } + main.post { if (!stale() && !released) hand(file) } } } @@ -198,50 +245,22 @@ class SplatKitView(private val reactContext: ThemedReactContext) : else -> null } - /** - * Worlds are tens to hundreds of megabytes, so the bytes are read here and - * never serialised through the bridge. - */ - private fun readBytes(uri: String): ByteArray = when { - uri.startsWith("asset://") -> - reactContext.assets.open(uri.removePrefix("asset://")).use { it.readBytes() } - - uri.startsWith("http://") || uri.startsWith("https://") -> - URL(uri).openStream().use { it.readBytes() } - - uri.startsWith("${ContentResolver.SCHEME_CONTENT}://") -> - reactContext.contentResolver.openInputStream(Uri.parse(uri)) - ?.use { it.readBytes() } - ?: throw IllegalArgumentException("the content provider returned nothing") - - else -> throw IllegalArgumentException("unsupported source") - } + private var appliedQuality: RenderQuality? = null /** A preset plus overrides, or the engine's default when the prop is absent. */ fun setQuality(map: ReadableMap?) { - val presetName = map?.takeIf { it.hasKey("preset") }?.getString("preset") - val preset = when (presetName) { - null -> RenderQuality.HIGH - else -> RenderQuality.named(presetName) - ?: throw IllegalArgumentException("unknown quality preset: $presetName") - } - val quality = if (map == null) preset else preset.copy( - renderScale = map.floatOr("renderScale", preset.renderScale), - shDegree = map.intOr("shDegree", preset.shDegree), - splatBudget = map.intOr("splatBudget", preset.splatBudget), - cullMarginDegrees = map.floatOr("cullMarginDegrees", preset.cullMarginDegrees), - linearBlending = if (map.hasKey("linearBlending")) map.getBoolean("linearBlending") else preset.linearBlending, - ) + val quality = QualityMapper.fromMap(map) { Log.w(TAG, it) } + // A literal object in JSX is a new map on every render; the engine only + // hears about an actual change. + if (quality == appliedQuality) return + appliedQuality = quality surface.applyQuality(quality) } - private fun ReadableMap.floatOr(key: String, fallback: Float) = - if (hasKey(key)) getDouble(key).toFloat() else fallback - - private fun ReadableMap.intOr(key: String, fallback: Int) = - if (hasKey(key)) getInt(key) else fallback - fun setDeclaredPose(pose: CameraPose?) { + // A literal in JSX arrives as a new object on every render; only a + // different pose is a teleport. + if (pose == declaredPose) return declaredPose = pose if (pose != null && worldReady) surface.cameraPose = pose } @@ -257,45 +276,24 @@ class SplatKitView(private val reactContext: ThemedReactContext) : fun setStatsInterval(millis: Int) { statsIntervalMs = millis - if (millis > 0) startStatsTicker() else statsTicking = false + syncStatsTicker() } - private fun startStatsTicker() { - if (statsTicking) return - statsTicking = true - val tick = object : Runnable { - override fun run() { - if (!statsTicking || statsIntervalMs <= 0) { - statsTicking = false - return - } - surface.readStats(stats) - emit("topStats", Arguments.createMap().apply { - putDouble("fps", stats.fps.toDouble()) - putDouble("frameMs", stats.frameMillis.toDouble()) - putDouble("gpuMs", stats.gpuMillis.toDouble()) - putDouble("sortMs", stats.sortMillis.toDouble()) - putInt("splatCount", stats.splatCount) - val pose = surface.cameraPose - putMap("pose", Arguments.createMap().apply { - putDouble("x", pose.x.toDouble()) - putDouble("y", pose.y.toDouble()) - putDouble("z", pose.z.toDouble()) - putDouble("yaw", pose.yaw.toDouble()) - putDouble("pitch", pose.pitch.toDouble()) - }) - }) - main.postDelayed(this, statsIntervalMs.toLong()) - } + private fun syncStatsTicker() { + main.removeCallbacks(statsTick) + if (statsIntervalMs > 0 && running && !released) { + main.postDelayed(statsTick, statsIntervalMs.toLong()) } - main.postDelayed(tick, statsIntervalMs.toLong()) } /** Called when React Native drops the view; the engine's resources go with it. */ fun release() { - statsTicking = false + released = true + worldGeneration.incrementAndGet() + colliderGeneration.incrementAndGet() running = false main.removeCallbacksAndMessages(null) + fetcher.disconnect() io.shutdownNow() reactContext.removeLifecycleEventListener(this) surface.listener = null @@ -313,9 +311,18 @@ class SplatKitView(private val reactContext: ThemedReactContext) : } override fun onHostDestroy() { /* release() runs when the view is dropped */ } + private companion object { + const val TAG = "SplatKit" + } + private fun emit(name: String, payload: WritableMap) { - UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) - ?.dispatchEvent(SplatEvent(UIManagerHelper.getSurfaceId(this), id, name, payload)) + if (released) return + val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) + if (dispatcher == null) { + Log.w(TAG, "dropped $name: no event dispatcher for view $id") + return + } + dispatcher.dispatchEvent(SplatEvent(UIManagerHelper.getSurfaceId(this), id, name, payload)) } } diff --git a/android/src/main/java/com/splatkit/reactnative/SplatViewManager.kt b/android/src/main/java/com/splatkit/reactnative/SplatViewManager.kt index acc1c5a..322e210 100644 --- a/android/src/main/java/com/splatkit/reactnative/SplatViewManager.kt +++ b/android/src/main/java/com/splatkit/reactnative/SplatViewManager.kt @@ -1,6 +1,5 @@ package com.splatkit.reactnative -import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager @@ -88,14 +87,8 @@ class SplatViewManager : view.startBenchmark(seconds.toFloat()) } - // The delegate routes commands on the new architecture; this keeps the view - // usable through the interop layer as well. - override fun receiveCommand(view: SplatKitView, command: String, args: ReadableArray?) { - delegate.receiveCommand(view, command, args) - } - - // Codegen wires these on the new architecture; declaring them keeps the view - // working through the interop layer too. + // Codegen names the events on the JavaScript side; the view manager registry + // still asks for them here, and an unnamed direct event is silently dropped. override fun getExportedCustomDirectEventTypeConstants(): MutableMap = mutableMapOf( "topEngineReady" to mapOf("registrationName" to "onEngineReady"), @@ -103,18 +96,23 @@ class SplatViewManager : "topWorldFailed" to mapOf("registrationName" to "onWorldFailed"), "topColliderReady" to mapOf("registrationName" to "onColliderReady"), "topColliderFailed" to mapOf("registrationName" to "onColliderFailed"), + "topLoadProgress" to mapOf("registrationName" to "onLoadProgress"), "topStats" to mapOf("registrationName" to "onStats"), ) companion object { const val NAME = "SplatView" + /** A missing or null coordinate is 0 rather than a crash; the prop is typed, so this only guards the interop path. */ + private fun ReadableMap.floatOrZero(key: String): Float = + if (hasKey(key) && !isNull(key)) getDouble(key).toFloat() else 0f + private fun poseOf(map: ReadableMap) = CameraPose( - x = map.getDouble("x").toFloat(), - y = map.getDouble("y").toFloat(), - z = map.getDouble("z").toFloat(), - yaw = if (map.hasKey("yaw")) map.getDouble("yaw").toFloat() else 0f, - pitch = if (map.hasKey("pitch")) map.getDouble("pitch").toFloat() else 0f, + x = map.floatOrZero("x"), + y = map.floatOrZero("y"), + z = map.floatOrZero("z"), + yaw = map.floatOrZero("yaw"), + pitch = map.floatOrZero("pitch"), ) } } diff --git a/android/src/test/java/com/splatkit/reactnative/QualityMapperTest.kt b/android/src/test/java/com/splatkit/reactnative/QualityMapperTest.kt new file mode 100644 index 0000000..000fd6a --- /dev/null +++ b/android/src/test/java/com/splatkit/reactnative/QualityMapperTest.kt @@ -0,0 +1,93 @@ +package com.splatkit.reactnative + +import com.facebook.react.bridge.JavaOnlyMap +import com.splatkit.RenderQuality +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class QualityMapperTest { + private val warnings = mutableListOf() + + private fun map(vararg pairs: Pair) = + JavaOnlyMap.of(*pairs.flatMap { listOf(it.first, it.second) }.toTypedArray()) + + @Test + fun `null map is the default preset`() { + assertEquals(RenderQuality.HIGH, QualityMapper.fromMap(null, warnings::add)) + } + + @Test + fun `unset sentinels keep the preset`() { + val q = QualityMapper.fromMap( + map( + "preset" to "medium", "renderScale" to -1.0, "shDegree" to -1, + "splatBudget" to -1, "cullMarginDegrees" to -1.0, "linearBlending" to -1, + ), + warnings::add, + ) + assertEquals(RenderQuality.MEDIUM, q) + assertTrue(warnings.isEmpty()) + } + + @Test + fun `overrides replace the preset's values`() { + val q = QualityMapper.fromMap( + map( + "preset" to "low", "renderScale" to 0.8, "shDegree" to -1, + "splatBudget" to -1, "cullMarginDegrees" to -1.0, "linearBlending" to 1, + ), + warnings::add, + ) + assertEquals(RenderQuality.LOW.copy(renderScale = 0.8f, linearBlending = true), q) + } + + @Test + fun `unknown preset falls back to high and warns instead of throwing`() { + val q = QualityMapper.fromMap(map("preset" to "ulta"), warnings::add) + assertEquals(RenderQuality.HIGH, q) + assertEquals(1, warnings.size) + assertTrue(warnings.first().contains("ulta")) + } + + @Test + fun `missing keys mean unset too`() { + val q = QualityMapper.fromMap(map("preset" to "ultra"), warnings::add) + assertEquals(RenderQuality.ULTRA, q) + } + + @Test + fun `linearBlending 0 turns the preset's blending off`() { + val q = QualityMapper.fromMap(map("preset" to "ultra", "linearBlending" to 0), warnings::add) + assertFalse(q.linearBlending) + assertEquals(RenderQuality.ULTRA.copy(linearBlending = false), q) + assertTrue(warnings.isEmpty()) + } + + @Test + fun `wrong types warn instead of throwing`() { + val q = QualityMapper.fromMap( + map("preset" to 42, "shDegree" to "two", "renderScale" to true, "linearBlending" to "yes"), + warnings::add, + ) + assertEquals(RenderQuality.HIGH, q) + assertEquals(4, warnings.size) + } + + @Test + fun `a negative value other than the sentinel is reported, not absorbed`() { + val q = QualityMapper.fromMap(map("preset" to "high", "renderScale" to -0.5, "shDegree" to -2), warnings::add) + assertEquals(RenderQuality.HIGH, q) + assertEquals(2, warnings.size) + assertNotEquals(RenderQuality.HIGH.copy(renderScale = -0.5f), q) + } + + @Test + fun `an explicit null is unset`() { + val q = QualityMapper.fromMap(map("preset" to "medium").apply { putNull("renderScale") }, warnings::add) + assertEquals(RenderQuality.MEDIUM, q) + assertTrue(warnings.isEmpty()) + } +} diff --git a/android/src/test/java/com/splatkit/reactnative/SourceFetcherTest.kt b/android/src/test/java/com/splatkit/reactnative/SourceFetcherTest.kt new file mode 100644 index 0000000..5388116 --- /dev/null +++ b/android/src/test/java/com/splatkit/reactnative/SourceFetcherTest.kt @@ -0,0 +1,189 @@ +package com.splatkit.reactnative + +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.File +import java.net.ServerSocket +import java.net.Socket +import kotlin.concurrent.thread +import java.util.concurrent.CancellationException +import java.util.concurrent.atomic.AtomicInteger + +class SourceFetcherTest { + @get:Rule val folder = TemporaryFolder() + + private lateinit var server: ServerSocket + private val requests = AtomicInteger(0) + private val body = ByteArray(1_000_000) { (it % 251).toByte() } + + private fun fetcher() = SourceFetcher(folder.root) { null } + private fun url(path: String) = "http://127.0.0.1:${server.localPort}$path" + private fun cacheFiles() = File(folder.root, "splatkit").listFiles()?.map { it.name } ?: emptyList() + + /** + * The smallest HTTP/1.0 server that can answer the four cases the fetcher + * has to survive: a whole body, a truncated one, a 404 and one with no + * declared length. The JDK's own server is not on this module's test + * classpath, which is built against android.jar. + */ + @Before + fun serve() { + server = ServerSocket(0, 50, java.net.InetAddress.getLoopbackAddress()) + thread(isDaemon = true) { + while (!server.isClosed) { + val socket = try { server.accept() } catch (e: IOException) { return@thread } + thread(isDaemon = true) { answer(socket) } + } + } + } + + private fun answer(socket: Socket) = socket.use { s -> + val request = s.getInputStream().bufferedReader().readLine() ?: return@use + val path = request.split(' ')[1] + val out = s.getOutputStream() + fun head(status: String, length: Long?) { + val lengthLine = if (length != null) "Content-Length: $length\r\n" else "" + out.write("HTTP/1.0 $status\r\n${lengthLine}Connection: close\r\n\r\n".toByteArray()) + } + when (path) { + "/world.spz" -> { requests.incrementAndGet(); head("200 OK", body.size.toLong()); out.write(body) } + "/short.spz" -> { head("200 OK", body.size.toLong()); out.write(body, 0, 400_000) } + "/missing.spz" -> head("404 Not Found", 0) + "/unsized.spz" -> { head("200 OK", null); out.write(body, 0, 1234) } + else -> head("404 Not Found", 0) + } + out.flush() + } + + @After + fun stop() = server.close() + + @Test + fun `a whole body lands in the cache with a final progress event`() { + val events = mutableListOf>() + val file = fetcher().fetch(url("/world.spz"), { false }) { b, t -> events += b to t } + assertArrayEquals(body, file.readBytes()) + assertEquals(".spz", file.name.takeLast(4)) + assertEquals(body.size.toLong() to body.size.toLong(), events.last()) + assertEquals(listOf(file.name), cacheFiles()) + } + + @Test + fun `a second fetch of the same uri is served from the cache`() { + val f = fetcher() + val first = f.fetch(url("/world.spz"), { false }) { _, _ -> } + val events = mutableListOf>() + val second = f.fetch(url("/world.spz"), { false }) { b, t -> events += b to t } + assertEquals(first, second) + assertEquals(1, requests.get()) + assertEquals(listOf(body.size.toLong() to body.size.toLong()), events) + } + + @Test + fun `a body shorter than the declared length is not cached`() { + val e = runCatching { fetcher().fetch(url("/short.spz"), { false }) { _, _ -> } }.exceptionOrNull() + assertTrue("expected an IOException, got $e", e is IOException) + assertTrue(e!!.message!!.contains("truncated")) + assertEquals(emptyList(), cacheFiles()) + } + + @Test + fun `a non 2xx answer throws with the code and writes nothing`() { + val e = runCatching { fetcher().fetch(url("/missing.spz"), { false }) { _, _ -> } }.exceptionOrNull() + assertTrue(e is IOException) + assertTrue(e!!.message!!.contains("404")) + assertEquals(emptyList(), cacheFiles()) + } + + @Test + fun `an unknown length still ends with a final progress event`() { + val events = mutableListOf>() + fetcher().fetch(url("/unsized.spz"), { false }) { b, t -> events += b to t } + assertEquals(1234L to 1234L, events.last()) + } + + @Test + fun `cancelling mid stream leaves neither a part nor a target`() { + var calls = 0 + val e = runCatching { + fetcher().fetch(url("/world.spz"), { ++calls > 1 }) { _, _ -> } + }.exceptionOrNull() + assertTrue("expected CancellationException, got $e", e is CancellationException) + assertEquals(emptyList(), cacheFiles()) + } + + @Test + fun `two fetchers over one directory do not share a part file`() { + val a = fetcher() + val b = fetcher() + val fromA = a.fetch(url("/world.spz"), { false }) { _, _ -> } + val fromB = b.fetch(url("/world.spz"), { false }) { _, _ -> } + assertEquals(fromA, fromB) + assertArrayEquals(body, fromB.readBytes()) + } + + @Test + fun `platform sources go through the opener and are cached by uri`() { + val bytes = "glb".toByteArray() + val f = SourceFetcher(folder.root) { uri -> + if (uri.startsWith("asset://")) ByteArrayInputStream(bytes) to -1L else null + } + val file = f.fetch("asset://collider.glb", { false }) { _, _ -> } + assertArrayEquals(bytes, file.readBytes()) + assertEquals(".glb", file.name.takeLast(4)) + val e = runCatching { f.fetch("ftp://x", { false }) { _, _ -> } }.exceptionOrNull() + assertTrue(e is IOException && e.message!!.contains("unsupported")) + } + + @Test + fun `extension comes from the last path segment, ignoring query and fragment`() { + assertEquals(".spz", SourceFetcher.extensionOf("https://h/a/b/world.spz?v=2#x")) + assertEquals(".glb", SourceFetcher.extensionOf("asset://collider.glb")) + assertEquals("", SourceFetcher.extensionOf("content://provider/doc/123")) + assertEquals("", SourceFetcher.extensionOf("https://h/.hidden")) + } + + @Test + fun `a fresh fetcher does not sweep a part file another view is writing`() { + val dir = File(folder.root, "splatkit").apply { mkdirs() } + val live = File(dir, "abc.spz123.part").apply { writeText("x") } + val old = File(dir, "old.spz456.part").apply { writeText("x"); setLastModified(System.currentTimeMillis() - 2 * 60 * 60 * 1000L) } + fetcher() + assertTrue(live.exists()) + assertFalse(old.exists()) + } + + @Test + fun `finished files over the cap go oldest first and part files are left alone`() { + val dir = File(folder.root, "splatkit").apply { mkdirs() } + val now = System.currentTimeMillis() + val oldest = File(dir, "a.spz").apply { writeBytes(ByteArray(300)); setLastModified(now - 3000) } + val middle = File(dir, "b.spz").apply { writeBytes(ByteArray(300)); setLastModified(now - 2000) } + val newest = File(dir, "c.spz").apply { writeBytes(ByteArray(300)); setLastModified(now - 1000) } + val part = File(dir, "d.spz1.part").apply { writeBytes(ByteArray(300)) } + SourceFetcher(folder.root, cacheCapBytes = 700) { null } + assertFalse(oldest.exists()) + assertTrue(middle.exists()) + assertTrue(newest.exists()) + assertTrue(part.exists()) + } + + @Test + fun `a cache hit counts as use so the file is evicted last`() { + val cached = fetcher().fetch(url("/world.spz"), { false }) { _, _ -> } + val lastWeek = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L + cached.setLastModified(lastWeek) + fetcher().fetch(url("/world.spz"), { false }) { _, _ -> } + assertTrue(cached.lastModified() > lastWeek) + assertEquals(1, requests.get()) + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 16b00bb..c4103c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,6 @@ export default defineConfig([ }, }, { - ignores: ['node_modules/', 'lib/'], + ignores: ['node_modules/', 'lib/', 'android/build/', 'example/android/build/', 'example/android/app/build/'], }, ]); diff --git a/example/README.md b/example/README.md index 3e2c3f8..728781a 100644 --- a/example/README.md +++ b/example/README.md @@ -1,97 +1,14 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). +# react-native-splatkit example -# Getting Started +Walks a World Labs world with an on screen joystick, switches presets live and shows the engine's stats. -> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. - -## Step 1: Start Metro - -First, you will need to run **Metro**, the JavaScript build tool for React Native. - -To start the Metro dev server, run the following command from the root of your React Native project: - -```sh -# Using npm -npm start - -# OR using Yarn -yarn start -``` - -## Step 2: Build and run your app - -With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: - -### Android - -```sh -# Using npm -npm run android - -# OR using Yarn -yarn android -``` - -### iOS - -For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). - -The first time you create a new project, run the Ruby bundler to install CocoaPods itself: - -```sh -bundle install -``` - -Then, and every time you update your native dependencies, run: +Push a world and its collider into the app's own directory, then run it on a physical arm64 device: ```sh -bundle exec pod install +adb push kitchen.spz /sdcard/Android/data/splatkit.example/files/world.spz +adb push kitchen.glb /sdcard/Android/data/splatkit.example/files/collider.glb +yarn && yarn example android ``` -For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). - -```sh -# Using npm -npm run ios - -# OR using Yarn -yarn ios -``` - -If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. - -This is one way to run your app — you can also build it directly from Android Studio or Xcode. - -## Step 3: Modify your app - -Now that you have successfully run the app, let's make changes! - -Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). - -When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: - -- **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). -- **iOS**: Press R in iOS Simulator. - -## Congratulations! :tada: - -You've successfully run and modified your React Native App. :partying_face: - -### Now what? - -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). - -# Troubleshooting - -If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. - -# Learn More - -To learn more about React Native, take a look at the following resources: - -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. +The example resolves the library from the workspace source through the `react-native-splatkit-source` condition in `metro.config.js`, so edits to `src/` show up on reload. +Release builds run with R8 on so the example proves the published engine survives minification. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 0000000..6442c14 --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,2080 @@ +PODS: + - FBLazyVector (0.85.0) + - hermes-engine (250829098.0.10): + - hermes-engine/Pre-built (= 250829098.0.10) + - hermes-engine/Pre-built (250829098.0.10) + - RCTDeprecation (0.85.0) + - RCTRequired (0.85.0) + - RCTSwiftUI (0.85.0) + - RCTSwiftUIWrapper (0.85.0): + - RCTSwiftUI + - RCTTypeSafety (0.85.0): + - FBLazyVector (= 0.85.0) + - RCTRequired (= 0.85.0) + - React-Core (= 0.85.0) + - React (0.85.0): + - React-Core (= 0.85.0) + - React-Core/DevSupport (= 0.85.0) + - React-Core/RCTWebSocket (= 0.85.0) + - React-RCTActionSheet (= 0.85.0) + - React-RCTAnimation (= 0.85.0) + - React-RCTBlob (= 0.85.0) + - React-RCTImage (= 0.85.0) + - React-RCTLinking (= 0.85.0) + - React-RCTNetwork (= 0.85.0) + - React-RCTSettings (= 0.85.0) + - React-RCTText (= 0.85.0) + - React-RCTVibration (= 0.85.0) + - React-callinvoker (0.85.0) + - React-Core (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.0) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core-prebuilt (0.85.0): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/Default (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/DevSupport (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.0) + - React-Core/RCTWebSocket (= 0.85.0) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTActionSheetHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTAnimationHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTBlobHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTImageHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTLinkingHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTNetworkHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTSettingsHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTTextHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTVibrationHeaders (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTWebSocket (0.85.0): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.0) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-CoreModules (0.85.0): + - RCTTypeSafety (= 0.85.0) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.85.0) + - React-debug + - React-jsi (= 0.85.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.85.0) + - React-runtimeexecutor + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-cxxreact (0.85.0): + - hermes-engine + - React-callinvoker (= 0.85.0) + - React-Core-prebuilt + - React-debug (= 0.85.0) + - React-jsi (= 0.85.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.85.0) + - React-perflogger (= 0.85.0) + - React-runtimeexecutor + - React-timing (= 0.85.0) + - React-utils + - ReactNativeDependencies + - React-debug (0.85.0) + - React-defaultsnativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-domnativemodule + - React-Fabric/animated + - React-featureflags + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-RCTFBReactNativeSpec + - React-webperformancenativemodule + - ReactNativeDependencies + - Yoga + - React-domnativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-Fabric + - React-Fabric/bridging + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animated (= 0.85.0) + - React-Fabric/animationbackend (= 0.85.0) + - React-Fabric/animations (= 0.85.0) + - React-Fabric/attributedstring (= 0.85.0) + - React-Fabric/bridging (= 0.85.0) + - React-Fabric/componentregistry (= 0.85.0) + - React-Fabric/componentregistrynative (= 0.85.0) + - React-Fabric/components (= 0.85.0) + - React-Fabric/consistency (= 0.85.0) + - React-Fabric/core (= 0.85.0) + - React-Fabric/dom (= 0.85.0) + - React-Fabric/imagemanager (= 0.85.0) + - React-Fabric/leakchecker (= 0.85.0) + - React-Fabric/mounting (= 0.85.0) + - React-Fabric/observers (= 0.85.0) + - React-Fabric/scheduler (= 0.85.0) + - React-Fabric/telemetry (= 0.85.0) + - React-Fabric/uimanager (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animated (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animationbackend (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/bridging (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistry (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.85.0) + - React-Fabric/components/root (= 0.85.0) + - React-Fabric/components/scrollview (= 0.85.0) + - React-Fabric/components/view (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/root (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/view (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric/consistency (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/imagemanager (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/leakchecker (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/mounting (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.85.0) + - React-Fabric/observers/intersection (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/events (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/intersection (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/scheduler (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancecdpmetrics + - React-performancetimeline + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/telemetry (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-FabricComponents (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.85.0) + - React-FabricComponents/textlayoutmanager (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.85.0) + - React-FabricComponents/components/iostextinput (= 0.85.0) + - React-FabricComponents/components/modal (= 0.85.0) + - React-FabricComponents/components/rncore (= 0.85.0) + - React-FabricComponents/components/safeareaview (= 0.85.0) + - React-FabricComponents/components/scrollview (= 0.85.0) + - React-FabricComponents/components/switch (= 0.85.0) + - React-FabricComponents/components/text (= 0.85.0) + - React-FabricComponents/components/textinput (= 0.85.0) + - React-FabricComponents/components/unimplementedview (= 0.85.0) + - React-FabricComponents/components/virtualview (= 0.85.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/inputaccessory (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/iostextinput (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/modal (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/rncore (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/safeareaview (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/scrollview (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/switch (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/text (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/textinput (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/unimplementedview (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualview (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.85.0): + - hermes-engine + - RCTRequired (= 0.85.0) + - RCTTypeSafety (= 0.85.0) + - React-Core-prebuilt + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.85.0) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-featureflags (0.85.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-featureflagsnativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-graphics (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-utils + - ReactNativeDependencies + - React-hermes (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.0) + - React-jsi + - React-jsiexecutor (= 0.85.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.85.0) + - React-runtimeexecutor + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-ImageManager (0.85.0): + - React-Core-prebuilt + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-intersectionobservernativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-jserrorhandler (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - ReactNativeDependencies + - React-jsi (0.85.0): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-jserrorhandler + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspector (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.85.0) + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspectorcdp (0.85.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsinspectornetwork (0.85.0): + - React-Core-prebuilt + - React-jsinspectorcdp + - ReactNativeDependencies + - React-jsinspectortracing (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsinspectornetwork + - React-oscompat + - React-timing + - ReactNativeDependencies + - React-jsitooling (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.0) + - React-debug + - React-jsi (= 0.85.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsitracing (0.85.0): + - React-jsi + - React-logger (0.85.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.85.0): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-microtasksnativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-NativeModulesApple (0.85.0): + - hermes-engine + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-networking (0.85.0): + - React-Core-prebuilt + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-oscompat (0.85.0) + - React-perflogger (0.85.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancecdpmetrics (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing + - ReactNativeDependencies + - React-performancetimeline (0.85.0): + - React-Core-prebuilt + - React-featureflags + - React-jsinspector + - React-jsinspectortracing + - React-perflogger + - React-timing + - ReactNativeDependencies + - React-RCTActionSheet (0.85.0): + - React-Core/RCTActionSheetHeaders (= 0.85.0) + - React-RCTAnimation (0.85.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders + - React-debug + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTAppDelegate (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-RCTBlob (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTFabric (0.85.0): + - hermes-engine + - RCTSwiftUIWrapper + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-networking + - React-performancecdpmetrics + - React-performancetimeline + - React-RCTAnimation + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-RCTFBReactNativeSpec (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.85.0) + - ReactCommon + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.85.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTLinking (0.85.0): + - React-Core/RCTLinkingHeaders (= 0.85.0) + - React-jsi (= 0.85.0) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.85.0) + - React-RCTNetwork (0.85.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-networking + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTRuntime (0.85.0): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-debug + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - ReactNativeDependencies + - React-RCTSettings (0.85.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTText (0.85.0): + - React-Core/RCTTextHeaders (= 0.85.0) + - Yoga + - React-RCTVibration (0.85.0): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-rendererconsistency (0.85.0) + - React-renderercss (0.85.0): + - React-debug + - React-utils + - React-rendererdebug (0.85.0): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-RuntimeApple (0.85.0): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-RuntimeCore (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-runtimeexecutor (0.85.0): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.85.0) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-runtimescheduler (0.85.0): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - ReactNativeDependencies + - React-timing (0.85.0): + - React-debug + - React-utils (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-debug + - React-jsi (= 0.85.0) + - ReactNativeDependencies + - React-webperformancenativemodule (0.85.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactAppDependencyProvider (0.85.0): + - ReactCodegen + - ReactCodegen (0.85.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactCommon (0.85.0): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.85.0) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.85.0): + - hermes-engine + - React-callinvoker (= 0.85.0) + - React-Core-prebuilt + - React-cxxreact (= 0.85.0) + - React-jsi (= 0.85.0) + - React-logger (= 0.85.0) + - React-perflogger (= 0.85.0) + - ReactCommon/turbomodule/bridging (= 0.85.0) + - ReactCommon/turbomodule/core (= 0.85.0) + - ReactNativeDependencies + - ReactCommon/turbomodule/bridging (0.85.0): + - hermes-engine + - React-callinvoker (= 0.85.0) + - React-Core-prebuilt + - React-cxxreact (= 0.85.0) + - React-jsi (= 0.85.0) + - React-logger (= 0.85.0) + - React-perflogger (= 0.85.0) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.85.0): + - hermes-engine + - React-callinvoker (= 0.85.0) + - React-Core-prebuilt + - React-cxxreact (= 0.85.0) + - React-debug (= 0.85.0) + - React-featureflags (= 0.85.0) + - React-jsi (= 0.85.0) + - React-logger (= 0.85.0) + - React-perflogger (= 0.85.0) + - React-utils (= 0.85.0) + - ReactNativeDependencies + - ReactNativeDependencies (0.85.0) + - Splatkit (0.1.0-alpha.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - Yoga (0.0.0) + +DEPENDENCIES: + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../node_modules/react-native/`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) + - Splatkit (from `../..`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + +EXTERNAL SOURCES: + FBLazyVector: + :path: "../node_modules/react-native/Libraries/FBLazyVector" + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-v250829098.0.10 + RCTDeprecation: + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../node_modules/react-native/Libraries/Required" + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: "../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" + React-oscompat: + :path: "../node_modules/react-native/ReactCommon/oscompat" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../node_modules/react-native/React" + React-RCTFBReactNativeSpec: + :path: "../node_modules/react-native/React" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../node_modules/react-native/React/Runtime" + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" + ReactAppDependencyProvider: + :path: build/generated/ios/ReactAppDependencyProvider + ReactCodegen: + :path: build/generated/ios/ReactCodegen + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" + Splatkit: + :path: "../.." + Yoga: + :path: "../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + FBLazyVector: c00c20551d40126351a6783c47ce75f5b374851b + hermes-engine: 7cd4b05db99bd78a9a93429fc621f58451c3e206 + RCTDeprecation: 3bb167081b134461cfeb875ff7ae1945f8635257 + RCTRequired: 74839f55d5058a133a0bc4569b0afec750957f64 + RCTSwiftUI: 87a316382f3eab4dd13d2a0d0fd2adcce917361a + RCTSwiftUIWrapper: a31d45fd2891c1e44c1912d9d0c0fac18ed275a0 + RCTTypeSafety: abdf2eaed5501a52f2000de668ccfc60b78c3b27 + React: 1b1536b9099195944034e65b1830f463caaa8390 + React-callinvoker: 6dff6d17d1d6cc8fdf85468a649bafed473c65f5 + React-Core: 00faa4d038298089a1d5a5b21dde8660c4f0820d + React-Core-prebuilt: 536d0af0925322c4d76cc028dddfa635723bebae + React-CoreModules: a17807f849bfd86045b0b9a75ec8c19373b482f6 + React-cxxreact: c7b53ace5827be54048288bce5c55f337c41e95f + React-debug: e1f00fcd2cef58a2897471a6d76a4ef5f5f90c74 + React-defaultsnativemodule: a4354f3bc1a8ef54b0a49de6cb730c8d206c3404 + React-domnativemodule: 18124c1b87708faa392e084ee4dfa600a903db5f + React-Fabric: be4ef16f85b9756a0b6a8b4957569981548d879d + React-FabricComponents: 8b9b88285710dbc17051975a794ca089080df1f8 + React-FabricImage: 3b52d895e2838a0d5ed8404ac6ebfdd6a517ad17 + React-featureflags: 2c85a987e70862d03332dd504840539a915722e3 + React-featureflagsnativemodule: 144f925ea93ea2397ad40acbd9c2487cadffc49d + React-graphics: 122811718b6ad23d49e2f25d59cee8ed87f1875a + React-hermes: 2def7f96a41a78196e65722619aca3d285a19c53 + React-idlecallbacksnativemodule: b2821ddb271d09285d9ee58eeda694e4fef3f036 + React-ImageManager: bf6936d4d0509992a2ceb3dfbd557b130add80ad + React-intersectionobservernativemodule: 6b28c3c069e801275d7c5822bd23a3bbcec16a60 + React-jserrorhandler: 37420717a46c0e5e9f2805cdbb76ab8898f3e5a2 + React-jsi: f4b843b6851635f64e2bc17fd176b33dec120ebc + React-jsiexecutor: eb8bd947675ef44bedf6d8990a23681fc758aff0 + React-jsinspector: 132ce1fa7aece674c656db3dcb9bfb2eaff354ca + React-jsinspectorcdp: fa5507da20ea181af5cf15531711a40ad9965dab + React-jsinspectornetwork: 2a83e372275c71e2fbd153e2cf3d0151e1a7cd8d + React-jsinspectortracing: 2f6cad1182c183f249a7a609de8123986b6c5a27 + React-jsitooling: 3bd2e214f61686c6d89d44baea32e6b8dd3cf425 + React-jsitracing: 6b3a3df0e1b28d65a402c68dc4b79d9abf91b41b + React-logger: ee47d5f3b59a46a006c65038ed5d0b1143e37510 + React-Mapbuffer: 7f8bfbe3fcb2203db4ccb3975414af8cabe4bcd0 + React-microtasksnativemodule: ca1f33f7c98b76d923f550d39631eb4e05fa9aec + React-NativeModulesApple: 9c1f8815ebd72cc1c75587fe588513f6dd9cb708 + React-networking: 8f75f882c6794e91e28b458b5bc1461034098c80 + React-oscompat: 5361d0fa7905ba1c3b3c5e7c464d6be9d2d85f4b + React-perflogger: 44ecaa45852241f80e07c0787c8b65516f5e774f + React-performancecdpmetrics: ef5be4428f221866215bf66ae0ed35d1892fab56 + React-performancetimeline: 1253e6fd3c9ab141f22903099c82b6c0d6fd9cff + React-RCTActionSheet: e3d1db66ef805645e83e6e80f2e21922328a79a7 + React-RCTAnimation: dc39e18331edfa4e4f3631ab83086ce4ba15c4bb + React-RCTAppDelegate: d08cad1065637eecaf347286807ca25d5e966396 + React-RCTBlob: bd5a11e3b206b86ccdcffa9538a5a4bea0acc0dd + React-RCTFabric: efea59a73331fe82c9a79d4b0b96dda3c4d1416b + React-RCTFBReactNativeSpec: 1adfc4557960efe5f233925b55d420e2483dd7df + React-RCTImage: 11407de524bafcc1790394728ca3fe40c6719093 + React-RCTLinking: 707855a5142f65472096a2910044444d390e8c96 + React-RCTNetwork: 8033c7c90b0983dcf994220a9bdeaef428f0b3da + React-RCTRuntime: 722152a3a55a2f89485f5bff358ab62ab1843d99 + React-RCTSettings: cf450b5c44e1d8379b06ac9469c8a81c35ecd7c4 + React-RCTText: 19f706ee0de06dd92945da223d8558d849209e9d + React-RCTVibration: 3383f98add29944aebdcdfa89598b09b1cadf13c + React-rendererconsistency: dd05e33df654b44cc5cdf99532b229d60d469b09 + React-renderercss: 2b6291db12578663d260d24f4c3ece458216a738 + React-rendererdebug: a06085705d5e796b1e40ac0f0e71b0868103e1a3 + React-RuntimeApple: 65fad601d27f99f258cb7b72f2e3b467298ea12c + React-RuntimeCore: f86d3cf1a66ceeed2767273899529c190b64efd4 + React-runtimeexecutor: 4022f2022adc7877b867ecc32bc2ba76f541d94f + React-RuntimeHermes: 003fc52c8419f74cdb7d72b426483131f9f6a09e + React-runtimescheduler: 93fc5f5ab550e39afc342158aa7e7ef324d13565 + React-timing: fca9c61f8a6ef76a4ef286a700ab54d3cdf3c079 + React-utils: 5717eed2e4c96e4d8ad0718c2f63494c06c19e47 + React-webperformancenativemodule: c465a47195d1c6f3cd7eeb84f38389124b62ac73 + ReactAppDependencyProvider: 5787b37b8e2e51dfeab697ec031cc7c4080dcea2 + ReactCodegen: b3184a229afd01e7f8058dd81b805b843caa2bf9 + ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360 + ReactNativeDependencies: d61f0a3d1e1e2173221e8f01effe68efda804f2d + Splatkit: d12cb0ab83752755b4b0b88fd1531ffa20ee642a + Yoga: cc155241ea30670878e270d52d74cf29487c4bbb + +PODFILE CHECKSUM: 324b017977cd3d510d13e7db42d89bccf7bd01e8 + +COCOAPODS: 1.15.2 diff --git a/example/ios/SplatkitExample.xcodeproj/project.pbxproj b/example/ios/SplatkitExample.xcodeproj/project.pbxproj index 762e528..34554b2 100644 --- a/example/ios/SplatkitExample.xcodeproj/project.pbxproj +++ b/example/ios/SplatkitExample.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0C80B921A6F3F58F76C31292 /* libPods-SplatkitExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-SplatkitExample.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 4F6DF81E468CD9ECC6EC8092 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -159,6 +160,7 @@ files = ( 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 4F6DF81E468CD9ECC6EC8092 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -271,7 +273,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "splatkit.example"; + PRODUCT_BUNDLE_IDENTIFIER = splatkit.example; PRODUCT_NAME = SplatkitExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -300,7 +302,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = "splatkit.example"; + PRODUCT_BUNDLE_IDENTIFIER = splatkit.example; PRODUCT_NAME = SplatkitExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_VERSION = 5.0; @@ -370,6 +372,10 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -377,8 +383,13 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; }; name = Debug; }; @@ -435,6 +446,10 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -442,8 +457,12 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/example/ios/SplatkitExample.xcworkspace/contents.xcworkspacedata b/example/ios/SplatkitExample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..aab51a0 --- /dev/null +++ b/example/ios/SplatkitExample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example/ios/SplatkitExample/Info.plist b/example/ios/SplatkitExample/Info.plist index e954ea9..94aaf08 100644 --- a/example/ios/SplatkitExample/Info.plist +++ b/example/ios/SplatkitExample/Info.plist @@ -28,7 +28,6 @@ NSAppTransportSecurity - NSAllowsArbitraryLoads NSAllowsLocalNetworking @@ -36,6 +35,8 @@ NSLocationWhenInUseUsageDescription + RCTNewArchEnabled + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/example/jest.config.js b/example/jest.config.js index 294be30..d35aff6 100644 --- a/example/jest.config.js +++ b/example/jest.config.js @@ -1,3 +1,12 @@ module.exports = { preset: '@react-native/jest-preset', + // The package is resolved from the workspace source, the same way + // metro.config.js does it through the react-native-splatkit-source condition. + // react and react-native are pinned to this app's copies so the source, which + // lives one level up, does not pull a second React from the root. + moduleNameMapper: { + '^react-native-splatkit$': '/../src/index.tsx', + '^react$': '/node_modules/react', + '^react-native$': '/node_modules/react-native', + }, }; diff --git a/example/package.json b/example/package.json index a535ca0..574a1f2 100644 --- a/example/package.json +++ b/example/package.json @@ -7,7 +7,8 @@ "ios": "react-native run-ios", "start": "react-native start", "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", - "build:ios": "react-native build-ios --mode Debug" + "build:ios": "react-native build-ios --mode Debug", + "test": "jest" }, "dependencies": { "react": "19.2.3", @@ -25,8 +26,11 @@ "@react-native/metro-config": "0.85.0", "@react-native/typescript-config": "0.85.0", "@types/react": "^19.2.0", + "@types/react-test-renderer": "^19.0.0", + "jest": "^29.7.0", "react-native-builder-bob": "^0.43.0", - "react-native-monorepo-config": "^0.4.0" + "react-native-monorepo-config": "^0.4.0", + "react-test-renderer": "19.2.3" }, "engines": { "node": ">= 22.11.0" diff --git a/example/src/App.tsx b/example/src/App.tsx index 998e6c7..dbde49e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -114,6 +114,15 @@ export default function App() { onWorldFailed={(e) => setStatus(`world failed: ${e.nativeEvent.message}`) } + onLoadProgress={(e) => { + const { kind, bytes, total } = e.nativeEvent; + const mb = (bytes / 1048576).toFixed(0); + setStatus( + total > 0 + ? `${kind}: ${mb} of ${(total / 1048576).toFixed(0)} MB` + : `${kind}: ${mb} MB` + ); + }} onColliderReady={() => setStatus((s) => `${s}, walking`)} onColliderFailed={(e) => { // Read the event before the updater runs: by then it has been recycled. diff --git a/example/src/__tests__/App.test.tsx b/example/src/__tests__/App.test.tsx new file mode 100644 index 0000000..46d2c4a --- /dev/null +++ b/example/src/__tests__/App.test.tsx @@ -0,0 +1,14 @@ +import { expect, test } from '@jest/globals'; +import { act, create } from 'react-test-renderer'; +import App from '../App'; + +// The point of this test is not the HUD; it is that a consumer's Jest, with +// the React Native preset, can import the package's build and mount the view. +test('the example renders with the native view mounted', async () => { + let tree!: ReturnType; + await act(async () => { + tree = create(); + }); + const json = JSON.stringify(tree.toJSON()); + expect(json).toContain('"type":"SplatView"'); +}); diff --git a/ios/SplatView.mm b/ios/SplatView.mm index 794cb8c..1657064 100644 --- a/ios/SplatView.mm +++ b/ios/SplatView.mm @@ -31,18 +31,31 @@ - (instancetype)initWithFrame:(CGRect)frame return self; } -- (void)didMoveToWindow +// Fabric hands over the emitter and attaches the view to a window in an +// order it does not promise, so whichever happens last announces. +- (void)announceIfReady { - [super didMoveToWindow]; if (self.window == nil || _announced) { return; } - _announced = YES; if (auto emitter = std::static_pointer_cast(_eventEmitter)) { + _announced = YES; emitter->onEngineReady({.available = false, .gpu = ""}); } } +- (void)didMoveToWindow +{ + [super didMoveToWindow]; + [self announceIfReady]; +} + +- (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter +{ + [super updateEventEmitter:eventEmitter]; + [self announceIfReady]; +} + - (void)prepareForRecycle { _announced = NO; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..0dad826 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,7 @@ +/** Pure unit tests for the JavaScript side; nothing here imports react-native. */ +module.exports = { + testEnvironment: 'node', + roots: ['/src'], + transform: { '\\.[jt]sx?$': 'babel-jest' }, + modulePathIgnorePatterns: ['/lib/', '/example/'], +}; diff --git a/package.json b/package.json index bf689a3..d2d3d6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-splatkit", - "version": "0.1.0", + "version": "0.1.0-alpha.1", "description": "React Native component for real-time Gaussian splatting: load an SPZ scene, pick a quality preset, and walk through it from JavaScript.", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", @@ -17,15 +17,14 @@ "lib", "android", "ios", - "cpp", "*.podspec", - "react-native.config.js", "!ios/build", "!android/build", "!android/gradle", "!android/gradlew", "!android/gradlew.bat", "!android/local.properties", + "!android/src/test", "!**/__tests__", "!**/__fixtures__", "!**/__mocks__", @@ -36,7 +35,8 @@ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "typecheck": "tsc", - "lint": "eslint \"**/*.{js,ts,tsx}\"" + "lint": "eslint \"**/*.{js,ts,tsx}\"", + "test": "jest" }, "keywords": [ "react-native", @@ -56,12 +56,17 @@ }, "author": "Juan Tupa (https://github.com/Xget7)", "license": "MIT", + "sideEffects": false, + "engines": { + "node": ">= 22.11.0" + }, "bugs": { "url": "https://github.com/Xget7/react-native-splatkit/issues" }, "homepage": "https://github.com/Xget7/react-native-splatkit#readme", "publishConfig": { - "registry": "https://registry.npmjs.org/" + "registry": "https://registry.npmjs.org/", + "access": "public" }, "devDependencies": { "@eslint/compat": "^2.1.0", @@ -69,12 +74,15 @@ "@eslint/js": "^10.0.1", "@react-native/babel-preset": "0.85.0", "@react-native/eslint-config": "0.85.0", + "@types/jest": "^29.5.14", "@types/react": "^19.2.0", + "babel-jest": "^29.7.0", "del-cli": "^7.0.0", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-ft-flow": "^3.0.11", "eslint-plugin-prettier": "^5.5.6", + "jest": "^29.7.0", "prettier": "^3.8.3", "react": "19.2.3", "react-native": "0.85.0", @@ -83,8 +91,8 @@ "typescript": "^6.0.3" }, "peerDependencies": { - "react": "*", - "react-native": "*" + "react": ">=19.0.0", + "react-native": ">=0.85.0" }, "workspaces": [ "example" @@ -110,7 +118,7 @@ }, "codegenConfig": { "name": "SplatViewSpec", - "type": "all", + "type": "components", "jsSrcsDir": "src", "android": { "javaPackageName": "com.splatkit.reactnative" diff --git a/src/SplatView.tsx b/src/SplatView.tsx index 5c4388f..6c02755 100644 --- a/src/SplatView.tsx +++ b/src/SplatView.tsx @@ -4,22 +4,31 @@ import NativeSplatView, { Commands, type NativeProps, } from './SplatViewNativeComponent'; +import { + normalizeQuality, + type QualityPreset, + type QualitySettings, +} from './quality'; +import type { CameraPose } from './SplatViewNativeComponent'; export type { SplatSource, - QualitySettings, CameraPose, + EngineReadyEvent, + WorldReadyEvent, + FailureEvent, + LoadProgressEvent, + StatsEvent, } from './SplatViewNativeComponent'; -import type { QualitySettings, CameraPose } from './SplatViewNativeComponent'; - -export type QualityPreset = 'low' | 'medium' | 'high' | 'ultra'; +export type { QualityPreset, QualitySettings } from './quality'; export type SplatViewProps = Omit & ViewProps & { /** * A preset name, or a preset plus overrides: * `quality="medium"` or `quality={{ preset: 'medium', renderScale: 0.8 }}`. - * `high` when omitted. + * `high` when omitted. Out of range values are clamped with a warning in + * development; an unknown preset falls back to `high`. */ quality?: QualityPreset | QualitySettings; }; @@ -46,29 +55,41 @@ export type SplatViewHandle = { * * The view has to have a size; a `SurfaceView` with no height renders nothing * and reports no error, so give it `flex: 1` or explicit dimensions. + * Children are not laid out; put a HUD in a sibling view. */ const SplatViewComponent = forwardRef( ({ quality, ...props }, ref) => { - const nativeRef = useRef>(null); + const nativeRef = useRef>(null); - useImperativeHandle(ref, () => ({ - setWalkVelocity(forward: number, right: number) { - if (nativeRef.current == null) return; - Commands.setWalkVelocity(nativeRef.current, forward, right); - }, - setCameraPose({ x, y, z, yaw = 0, pitch = 0 }: CameraPose) { - if (nativeRef.current == null) return; - Commands.setCameraPose(nativeRef.current, x, y, z, yaw, pitch); - }, - startBenchmark(seconds = 10) { - if (nativeRef.current == null) return; - Commands.startBenchmark(nativeRef.current, seconds); - }, - })); + useImperativeHandle( + ref, + () => ({ + setWalkVelocity(forward: number, right: number) { + if (nativeRef.current == null) return; + Commands.setWalkVelocity(nativeRef.current, forward, right); + }, + setCameraPose({ x, y, z, yaw = 0, pitch = 0 }: CameraPose) { + if (nativeRef.current == null) return; + Commands.setCameraPose(nativeRef.current, x, y, z, yaw, pitch); + }, + startBenchmark(seconds = 10) { + if (nativeRef.current == null) return; + Commands.startBenchmark(nativeRef.current, seconds); + }, + }), + [] + ); - const settings = - typeof quality === 'string' ? { preset: quality } : quality; - return ; + // Normalised on every render: it is a handful of comparisons, Fabric diffs + // the struct by value, and the Android side skips a value it already + // applied, so a memo here would only add a dependency list to keep in sync. + return ( + + ); } ); diff --git a/src/SplatViewNativeComponent.ts b/src/SplatViewNativeComponent.ts index 400085e..5d96f93 100644 --- a/src/SplatViewNativeComponent.ts +++ b/src/SplatViewNativeComponent.ts @@ -21,7 +21,7 @@ export type SplatSource = { uri: string; }; -type EngineReadyEvent = { +export type EngineReadyEvent = { /** False when the device could not start the renderer; the view stays blank. */ available: boolean; /** GPU name and graphics API version as the driver reports them, empty when unavailable. */ @@ -29,24 +29,17 @@ type EngineReadyEvent = { }; /** - * How much the renderer spends per frame. `preset` picks the engine's - * `RenderQuality.LOW`, `MEDIUM`, `HIGH` or `ULTRA`; every other field - * overrides one value of that preset. Omit a field to keep the preset's. - * The reason behind each preset and its frame times are in the engine's README. + * The fully populated quality struct the wrapper builds with `normalizeQuality`. + * Every field is present; -1 means "keep the preset's value". See `quality.ts`. */ -export type QualitySettings = { - /** `low`, `medium`, `high` (the default) or `ultra`. */ - preset?: string; - /** Fraction of the surface the splats are drawn at, 0.1 to 2. Above 1 supersamples. */ - renderScale?: CodegenTypes.Double; - /** Spherical harmonics degree drawn, 0 to 3, capped by what the world carries. Takes effect on the next frame. */ - shDegree?: CodegenTypes.Int32; - /** Most splats drawn per frame through the level of detail tree; 0 draws them all. Applies to worlds loaded after it is set. */ - splatBudget?: CodegenTypes.Int32; - /** Angular margin around the view kept drawn so a turn never meets an empty edge. */ - cullMarginDegrees?: CodegenTypes.Double; - /** Blend in linear light instead of the encoded space the training used. */ - linearBlending?: boolean; +type NativeQualityStruct = { + preset: string; + renderScale: CodegenTypes.Double; + shDegree: CodegenTypes.Int32; + splatBudget: CodegenTypes.Int32; + cullMarginDegrees: CodegenTypes.Double; + /** -1 unset, 0 false, 1 true; codegen has no optional boolean with a sentinel. */ + linearBlending: CodegenTypes.Int32; }; /** Where the camera stands, in meters, and where it looks, in radians. */ @@ -58,15 +51,23 @@ export type CameraPose = { pitch?: CodegenTypes.Double; }; -type WorldReadyEvent = { +export type WorldReadyEvent = { splatCount: CodegenTypes.Int32; }; -type FailureEvent = { +export type FailureEvent = { message: string; }; -type StatsEvent = { +export type LoadProgressEvent = { + /** `world` or `collider`. */ + kind: string; + bytes: CodegenTypes.Double; + /** -1 when the source does not say how big it is. */ + total: CodegenTypes.Double; +}; + +export type StatsEvent = { fps: CodegenTypes.Double; frameMs: CodegenTypes.Double; gpuMs: CodegenTypes.Double; @@ -87,7 +88,7 @@ export interface NativeProps extends ViewProps { collider?: SplatSource; /** Preset plus overrides; `high` with no overrides when omitted. */ - quality?: QualitySettings; + quality?: NativeQualityStruct; /** * Where the camera starts. Applied when it changes and again when the world * and the collider become ready, so it can be set before the world loads. @@ -108,6 +109,8 @@ export interface NativeProps extends ViewProps { onWorldFailed?: CodegenTypes.DirectEventHandler; onColliderReady?: CodegenTypes.DirectEventHandler; onColliderFailed?: CodegenTypes.DirectEventHandler; + /** Bytes copied so far for a source that is not a local file; at most every 100 ms. */ + onLoadProgress?: CodegenTypes.DirectEventHandler; onStats?: CodegenTypes.DirectEventHandler; } @@ -116,13 +119,13 @@ export type SplatViewNativeComponentType = HostComponent; interface NativeCommands { /** Continuous walking in meters per second, for an on screen joystick. */ setWalkVelocity: ( - viewRef: React.ElementRef, + viewRef: React.ComponentRef, forward: CodegenTypes.Double, right: CodegenTypes.Double ) => void; /** Teleport: position in meters, yaw and pitch in radians. */ setCameraPose: ( - viewRef: React.ElementRef, + viewRef: React.ComponentRef, x: CodegenTypes.Double, y: CodegenTypes.Double, z: CodegenTypes.Double, @@ -131,7 +134,7 @@ interface NativeCommands { ) => void; /** A reproducible turn; the frame time distribution lands in logcat under the tag SplatKit. */ startBenchmark: ( - viewRef: React.ElementRef, + viewRef: React.ComponentRef, seconds: CodegenTypes.Double ) => void; } diff --git a/src/__tests__/quality.test.ts b/src/__tests__/quality.test.ts new file mode 100644 index 0000000..36d33ab --- /dev/null +++ b/src/__tests__/quality.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { normalizeQuality, UNSET } from '../quality'; + +const noWarn = () => {}; + +describe('normalizeQuality', () => { + it('turns a preset name into a struct with every override unset', () => { + expect(normalizeQuality('medium', noWarn)).toEqual({ + preset: 'medium', + renderScale: UNSET, + shDegree: UNSET, + splatBudget: UNSET, + cullMarginDegrees: UNSET, + linearBlending: UNSET, + }); + }); + + it('defaults to high when nothing is given', () => { + expect(normalizeQuality(undefined, noWarn).preset).toBe('high'); + }); + + it('keeps overrides and marks the rest unset', () => { + const q = normalizeQuality( + { preset: 'low', renderScale: 0.8, linearBlending: true }, + noWarn + ); + expect(q).toEqual({ + preset: 'low', + renderScale: 0.8, + shDegree: UNSET, + splatBudget: UNSET, + cullMarginDegrees: UNSET, + linearBlending: 1, + }); + }); + + it('falls back to high and warns on an unknown preset', () => { + const warn = jest.fn(); + // A typo has to type check to reach here, so cast. + const q = normalizeQuality({ preset: 'ulta' as 'ultra' }, warn); + expect(q.preset).toBe('high'); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('ulta')); + }); + + it('clamps out of range values and warns', () => { + const warn = jest.fn(); + const q = normalizeQuality( + { + preset: 'high', + renderScale: 5, + shDegree: 7, + splatBudget: -3, + cullMarginDegrees: 200, + }, + warn + ); + expect(q.renderScale).toBe(2); + expect(q.shDegree).toBe(3); + expect(q.splatBudget).toBe(0); + expect(q.cullMarginDegrees).toBe(90); + expect(warn).toHaveBeenCalledTimes(4); + }); + + it('rounds a fractional shDegree and splatBudget', () => { + const q = normalizeQuality( + { preset: 'high', shDegree: 1.6, splatBudget: 1000.4 }, + noWarn + ); + expect(q.shDegree).toBe(2); + expect(q.splatBudget).toBe(1000); + }); + + it('treats NaN and Infinity as unset and warns', () => { + const warn = jest.fn(); + const q = normalizeQuality( + { preset: 'high', renderScale: NaN, cullMarginDegrees: Infinity }, + warn + ); + expect(q.renderScale).toBe(UNSET); + expect(q.cullMarginDegrees).toBe(UNSET); + expect(warn).toHaveBeenCalledTimes(2); + }); + + it('pins the unset sentinel to -1, which QualityMapper.kt hardcodes', () => { + expect(UNSET).toBe(-1); + }); + + it('sends an explicit false as 0, not unset', () => { + const q = normalizeQuality( + { preset: 'ultra', linearBlending: false }, + noWarn + ); + expect(q.linearBlending).toBe(0); + }); + + it('never emits a user value as the unset sentinel', () => { + const q = normalizeQuality( + { + preset: 'high', + renderScale: -1, + shDegree: -1, + splatBudget: -1, + cullMarginDegrees: -1, + }, + noWarn + ); + expect(q.renderScale).toBeGreaterThanOrEqual(0); + expect(q.shDegree).toBeGreaterThanOrEqual(0); + expect(q.splatBudget).toBeGreaterThanOrEqual(0); + expect(q.cullMarginDegrees).toBeGreaterThanOrEqual(0); + }); + + it('treats null like undefined and warns on a number', () => { + const warn = jest.fn(); + expect(normalizeQuality(null as unknown as undefined, warn).preset).toBe( + 'high' + ); + expect(warn).not.toHaveBeenCalled(); + expect(normalizeQuality(5 as unknown as 'high', warn).preset).toBe('high'); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('caps splatBudget at the 32 bit maximum', () => { + const q = normalizeQuality({ preset: 'high', splatBudget: 1e12 }, noWarn); + expect(q.splatBudget).toBe(2147483647); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index 754cb5b..c29ac83 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,4 +6,9 @@ export type { QualitySettings, QualityPreset, CameraPose, + EngineReadyEvent, + WorldReadyEvent, + FailureEvent, + LoadProgressEvent, + StatsEvent, } from './SplatView'; diff --git a/src/quality.ts b/src/quality.ts new file mode 100644 index 0000000..16f4227 --- /dev/null +++ b/src/quality.ts @@ -0,0 +1,136 @@ +/** + * `quality` as the app writes it, and as the native side reads it. + * + * The native struct is always fully populated: an override the app did not set + * is sent as `UNSET` (-1). Codegen on some platforms zero-fills struct fields + * that are absent, and 0 is a valid render scale and a valid budget, so + * absence cannot mean "keep the preset's". A negative number can. + */ + +export type QualityPreset = 'low' | 'medium' | 'high' | 'ultra'; + +export type QualitySettings = { + /** `low`, `medium`, `high` (the default) or `ultra`. */ + preset?: QualityPreset; + /** Fraction of the surface the splats are drawn at, 0.1 to 2. Above 1 supersamples. */ + renderScale?: number; + /** Spherical harmonics degree drawn, 0 to 3, capped by what the world carries. */ + shDegree?: number; + /** Most splats drawn per frame through the level of detail tree; 0 draws them all. Applies to worlds loaded after it is set. */ + splatBudget?: number; + /** Angular margin around the view kept drawn so a turn never meets an empty edge, 0 to 90. */ + cullMarginDegrees?: number; + /** Blend in linear light instead of the encoded space the training used. */ + linearBlending?: boolean; +}; + +/** What crosses to native. Every field present; `UNSET` keeps the preset's value. */ +export type NativeQuality = { + preset: string; + renderScale: number; + shDegree: number; + splatBudget: number; + cullMarginDegrees: number; + /** -1 unset, 0 false, 1 true. */ + linearBlending: number; +}; + +export const UNSET = -1; + +const PRESETS: readonly QualityPreset[] = ['low', 'medium', 'high', 'ultra']; +/** The native field is 32 bit; anything larger would saturate silently on the way over. */ +const INT32_MAX = 2147483647; + +type Warn = (message: string) => void; + +// One line per distinct problem for the life of the app, in development and +// in release alike: a clamped value changes what is drawn, and a store build +// with no record of it is the kind of bug nobody can attribute later. +const warned = new Set(); +const defaultWarn: Warn = (message) => { + if (warned.has(message)) return; + warned.add(message); + console.warn(`[react-native-splatkit] ${message}`); +}; + +function clamped( + name: string, + value: number | undefined, + min: number, + max: number, + integer: boolean, + warn: Warn +): number { + if (value === undefined) return UNSET; + if (!Number.isFinite(value)) { + warn(`quality.${name} is ${value}; ignoring it`); + return UNSET; + } + let v = integer ? Math.round(value) : value; + if (v < min || v > max) { + warn(`quality.${name} ${value} is outside ${min} to ${max}; clamping`); + v = Math.min(max, Math.max(min, v)); + } + return v; +} + +export function normalizeQuality( + quality: QualityPreset | QualitySettings | undefined, + warn: Warn = defaultWarn +): NativeQuality { + let settings: QualitySettings; + if (quality == null) { + settings = {}; + } else if (typeof quality === 'string') { + settings = { preset: quality }; + } else if (typeof quality === 'object') { + settings = quality; + } else { + warn(`quality must be a preset name or an object, got ${typeof quality}`); + settings = {}; + } + + let preset: QualityPreset = 'high'; + if (settings.preset !== undefined) { + if (PRESETS.includes(settings.preset)) { + preset = settings.preset; + } else { + warn(`unknown quality preset '${String(settings.preset)}'; using 'high'`); + } + } + + return { + preset, + renderScale: clamped( + 'renderScale', + settings.renderScale, + 0.1, + 2, + false, + warn + ), + shDegree: clamped('shDegree', settings.shDegree, 0, 3, true, warn), + splatBudget: clamped( + 'splatBudget', + settings.splatBudget, + 0, + INT32_MAX, + true, + warn + ), + cullMarginDegrees: clamped( + 'cullMarginDegrees', + settings.cullMarginDegrees, + 0, + 90, + false, + warn + ), + linearBlending: + settings.linearBlending === undefined + ? UNSET + : settings.linearBlending + ? 1 + : 0, + }; +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 3c0636a..3b7ea3a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig", - "exclude": ["example", "lib"] + "exclude": ["example", "lib", "src/__tests__"] } diff --git a/yarn.lock b/yarn.lock index 56e906f..8cb60dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,7 +39,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.25.2, @babel/core@npm:^7.29.0": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.25.2, @babel/core@npm:^7.29.0": version: 7.29.7 resolution: "@babel/core@npm:7.29.7" dependencies: @@ -76,7 +76,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.29.1, @babel/generator@npm:^7.29.7, @babel/generator@npm:^7.29.8": +"@babel/generator@npm:^7.29.1, @babel/generator@npm:^7.29.7, @babel/generator@npm:^7.29.8, @babel/generator@npm:^7.7.2": version: 7.29.8 resolution: "@babel/generator@npm:7.29.8" dependencies: @@ -290,7 +290,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": version: 7.29.8 resolution: "@babel/parser@npm:7.29.8" dependencies: @@ -513,7 +513,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.29.7": +"@babel/plugin-syntax-jsx@npm:^7.29.7, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.29.7 resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" dependencies: @@ -612,7 +612,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.29.7": +"@babel/plugin-syntax-typescript@npm:^7.29.7, @babel/plugin-syntax-typescript@npm:^7.7.2": version: 7.29.7 resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" dependencies: @@ -1529,6 +1529,13 @@ __metadata: languageName: node linkType: hard +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": version: 4.10.1 resolution: "@eslint-community/eslint-utils@npm:4.10.1" @@ -1738,13 +1745,68 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": version: 0.1.6 resolution: "@istanbuljs/schema@npm:0.1.6" checksum: 10c0/bb0d370bf3dd454d2f37f1bccb8921e2da99adacef2da56ef47850e25d7a4de69cf639ead8c189755aef38921369024b4afea3535a5c2ac9082b3e1171bcbc3a languageName: node linkType: hard +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + "@jest/create-cache-key-function@npm:^29.7.0": version: 29.7.0 resolution: "@jest/create-cache-key-function@npm:29.7.0" @@ -1766,6 +1828,25 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + "@jest/fake-timers@npm:^29.7.0": version: 29.7.0 resolution: "@jest/fake-timers@npm:29.7.0" @@ -1780,6 +1861,55 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -1789,6 +1919,41 @@ __metadata: languageName: node linkType: hard +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + "@jest/transform@npm:^29.7.0": version: 29.7.0 resolution: "@jest/transform@npm:29.7.0" @@ -1870,7 +2035,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -2509,7 +2674,7 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 @@ -2534,6 +2699,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:^29.5.14": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" @@ -2550,6 +2725,24 @@ __metadata: languageName: node linkType: hard +"@types/react-test-renderer@npm:^19.0.0": + version: 19.3.0 + resolution: "@types/react-test-renderer@npm:19.3.0" + dependencies: + "@types/react": "npm:*" + checksum: 10c0/37f5588f698a057a33c1f4d5fd28a042f3df7f29a3554ddebb7f9c8ecd3e5fba085e5b12d14b30734c30b51959ef17544650b2a9704d629d0af251f126d8ae73 + languageName: node + linkType: hard + +"@types/react@npm:*": + version: 19.3.0 + resolution: "@types/react@npm:19.3.0" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/fbcabf303f935ca9f36ed2dcf81751cf84744098c5a683c8a62a0fa7edd31714b48d5c46b12fd0014f7f2dfe4d9ec6de428a850748eec719a70294e51029551b + languageName: node + linkType: hard + "@types/react@npm:^19.2.0": version: 19.2.18 resolution: "@types/react@npm:19.2.18" @@ -2804,6 +2997,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + "ansi-fragments@npm:^0.2.1": version: 0.2.1 resolution: "ansi-fragments@npm:0.2.1" @@ -3418,6 +3620,13 @@ __metadata: languageName: node linkType: hard +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -3466,6 +3675,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.0.0": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -3522,6 +3738,20 @@ __metadata: languageName: node linkType: hard +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.3 + resolution: "collect-v8-coverage@npm:1.0.3" + checksum: 10c0/bc62ba251bcce5e3354a8f88fa6442bee56e3e612fec08d4dfcf66179b41ea0bf544b0f78c4ebc0f8050871220af95bb5c5578a6aef346feea155640582f09dc + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -3679,6 +3909,23 @@ __metadata: languageName: node linkType: hard +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -3746,7 +3993,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.0, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -3765,7 +4012,7 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.7.2": +"dedent@npm:^1.0.0, dedent@npm:^1.7.2": version: 1.7.2 resolution: "dedent@npm:1.7.2" peerDependencies: @@ -3784,7 +4031,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.3.0": +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 @@ -3865,6 +4112,20 @@ __metadata: languageName: node linkType: hard +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -3899,6 +4160,13 @@ __metadata: languageName: node linkType: hard +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + "emoji-regex@npm:^10.3.0": version: 10.6.0 resolution: "emoji-regex@npm:10.6.0" @@ -4519,6 +4787,26 @@ __metadata: languageName: node linkType: hard +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.3 resolution: "exponential-backoff@npm:3.1.3" @@ -4650,7 +4938,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.1.0": +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -4902,7 +5190,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.4": +"glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -5089,6 +5377,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + "http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" @@ -5159,6 +5454,18 @@ __metadata: languageName: node linkType: hard +"import-local@npm:^3.0.2": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -5338,6 +5645,13 @@ __metadata: languageName: node linkType: hard +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + "is-generator-function@npm:^1.0.10": version: 1.1.2 resolution: "is-generator-function@npm:1.1.2" @@ -5547,7 +5861,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.2.0": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b @@ -5567,6 +5881,51 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + "iterator.prototype@npm:^1.1.5": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" @@ -5581,6 +5940,143 @@ __metadata: languageName: node linkType: hard +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + "jest-environment-node@npm:^29.7.0": version: 29.7.0 resolution: "jest-environment-node@npm:29.7.0" @@ -5625,6 +6121,28 @@ __metadata: languageName: node linkType: hard +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + "jest-message-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-message-util@npm:29.7.0" @@ -5653,6 +6171,18 @@ __metadata: languageName: node linkType: hard +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + "jest-regex-util@npm:^29.6.3": version: 29.6.3 resolution: "jest-regex-util@npm:29.6.3" @@ -5660,6 +6190,120 @@ __metadata: languageName: node linkType: hard +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + "jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" @@ -5688,6 +6332,22 @@ __metadata: languageName: node linkType: hard +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + "jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" @@ -5700,6 +6360,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + "joi@npm:^17.2.1": version: 17.13.7 resolution: "joi@npm:17.13.7" @@ -5996,6 +6675,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + "makeerror@npm:1.0.12": version: 1.0.12 resolution: "makeerror@npm:1.0.12" @@ -6719,7 +7407,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2": +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -6861,6 +7549,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": version: 1.1.0 resolution: "possible-typed-array-names@npm:1.1.0" @@ -6900,7 +7597,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -6927,7 +7624,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.4.2": +"prompts@npm:^2.0.1, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -6955,6 +7652,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + "qs@npm:~6.15.1": version: 6.15.3 resolution: "qs@npm:6.15.3" @@ -7015,6 +7719,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^19.2.3": + version: 19.3.0 + resolution: "react-is@npm:19.3.0" + checksum: 10c0/99b16fc1009222ec0bbe05286566c865908430778ea6ba90dd418c7a11594c6cbb8db58432735b9203e3c6463e3eef5a399df1317ee2d75fd96b3a88cfaa662e + languageName: node + linkType: hard + "react-native-builder-bob@npm:^0.43.0": version: 0.43.0 resolution: "react-native-builder-bob@npm:0.43.0" @@ -7072,10 +7783,13 @@ __metadata: "@react-native/metro-config": "npm:0.85.0" "@react-native/typescript-config": "npm:0.85.0" "@types/react": "npm:^19.2.0" + "@types/react-test-renderer": "npm:^19.0.0" + jest: "npm:^29.7.0" react: "npm:19.2.3" react-native: "npm:0.85.0" react-native-builder-bob: "npm:^0.43.0" react-native-monorepo-config: "npm:^0.4.0" + react-test-renderer: "npm:19.2.3" languageName: unknown linkType: soft @@ -7088,12 +7802,15 @@ __metadata: "@eslint/js": "npm:^10.0.1" "@react-native/babel-preset": "npm:0.85.0" "@react-native/eslint-config": "npm:0.85.0" + "@types/jest": "npm:^29.5.14" "@types/react": "npm:^19.2.0" + babel-jest: "npm:^29.7.0" del-cli: "npm:^7.0.0" eslint: "npm:^9.39.4" eslint-config-prettier: "npm:^10.1.8" eslint-plugin-ft-flow: "npm:^3.0.11" eslint-plugin-prettier: "npm:^5.5.6" + jest: "npm:^29.7.0" prettier: "npm:^3.8.3" react: "npm:19.2.3" react-native: "npm:0.85.0" @@ -7101,8 +7818,8 @@ __metadata: turbo: "npm:^2.9.16" typescript: "npm:^6.0.3" peerDependencies: - react: "*" - react-native: "*" + react: ">=19.0.0" + react-native: ">=0.85.0" languageName: unknown linkType: soft @@ -7164,6 +7881,18 @@ __metadata: languageName: node linkType: hard +"react-test-renderer@npm:19.2.3": + version: 19.2.3 + resolution: "react-test-renderer@npm:19.2.3" + dependencies: + react-is: "npm:^19.2.3" + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/842b82239dbddbc536083a6260c3e1b0507c02a3400bd05879fc19160468fd0f8ab79fec5dceffa6113b131835cc7621212f8415b46ea5156ab66bbfd7e24297 + languageName: node + linkType: hard + "react@npm:19.2.3": version: 19.2.3 resolution: "react@npm:19.2.3" @@ -7281,6 +8010,15 @@ __metadata: languageName: node linkType: hard +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -7295,7 +8033,14 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.11": +"resolve.exports@npm:^2.0.0": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d + languageName: node + linkType: hard + +"resolve@npm:^1.20.0, resolve@npm:^1.22.11": version: 1.22.12 resolution: "resolve@npm:1.22.12" dependencies: @@ -7325,7 +8070,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": version: 1.22.12 resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" dependencies: @@ -7429,7 +8174,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.27.0": +"scheduler@npm:0.27.0, scheduler@npm:^0.27.0": version: 0.27.0 resolution: "scheduler@npm:0.27.0" checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 @@ -7445,7 +8190,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.7.3": +"semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -7655,6 +8400,16 @@ __metadata: languageName: node linkType: hard +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + "source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -7672,7 +8427,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0": +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -7735,6 +8490,16 @@ __metadata: languageName: node linkType: hard +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + "string-natural-compare@npm:^3.0.1": version: 3.0.1 resolution: "string-natural-compare@npm:3.0.1" @@ -7880,6 +8645,13 @@ __metadata: languageName: node linkType: hard +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -8067,6 +8839,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + "type-fest@npm:^0.7.1": version: 0.7.1 resolution: "type-fest@npm:0.7.1" @@ -8279,6 +9058,17 @@ __metadata: languageName: node linkType: hard +"v8-to-istanbul@npm:^9.0.1": + version: 9.3.0 + resolution: "v8-to-istanbul@npm:9.3.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -8580,7 +9370,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.6.2": +"yargs@npm:^17.3.1, yargs@npm:^17.6.2": version: 17.7.3 resolution: "yargs@npm:17.7.3" dependencies: