diff --git a/android/src/main/java/com/margelo/nitro/rive/DeprecationWarning.kt b/android/src/main/java/com/margelo/nitro/rive/DeprecationWarning.kt new file mode 100644 index 00000000..90efb5b7 --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/rive/DeprecationWarning.kt @@ -0,0 +1,14 @@ +package com.margelo.nitro.rive + +object DeprecationWarning { + private val warned = mutableSetOf() + + fun warn(method: String, replacement: String) { + if (warned.add(method)) { + RiveLog.w( + "Deprecation", + "'$method' is deprecated and blocks the calling thread. Use '$replacement' instead.", + ) + } + } +} diff --git a/android/src/main/java/com/margelo/nitro/rive/HybridRiveLogger.kt b/android/src/main/java/com/margelo/nitro/rive/HybridRiveLogger.kt new file mode 100644 index 00000000..552296e8 --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/rive/HybridRiveLogger.kt @@ -0,0 +1,16 @@ +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +@Keep +@DoNotStrip +class HybridRiveLogger : HybridRiveLoggerSpec() { + override fun setHandler(handler: (level: String, tag: String, message: String) -> Unit) { + RiveLog.handler = handler + } + + override fun resetHandler() { + RiveLog.handler = null + } +} diff --git a/android/src/main/java/com/margelo/nitro/rive/RiveLog.kt b/android/src/main/java/com/margelo/nitro/rive/RiveLog.kt new file mode 100644 index 00000000..c6c2ae73 --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/rive/RiveLog.kt @@ -0,0 +1,23 @@ +package com.margelo.nitro.rive + +import android.util.Log + +object RiveLog { + var handler: ((String, String, String) -> Unit)? = null + + fun e(tag: String, message: String) { + handler?.invoke("error", tag, message) ?: Log.e(tag, message) + } + + fun w(tag: String, message: String) { + handler?.invoke("warn", tag, message) ?: Log.w(tag, message) + } + + fun i(tag: String, message: String) { + handler?.invoke("info", tag, message) ?: Log.i(tag, message) + } + + fun d(tag: String, message: String) { + handler?.invoke("debug", tag, message) ?: Log.d(tag, message) + } +} diff --git a/android/src/new/java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt b/android/src/new/java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt index 952fd9a9..fd58ff73 100644 --- a/android/src/new/java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt +++ b/android/src/new/java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt @@ -134,11 +134,11 @@ object ExperimentalAssetLoader { } else if (data.matchesAt(8, 0x57, 0x45, 0x42, 0x50)) { AssetType.IMAGE // WebP (WEBP) } else { - Log.w(TAG, "Unknown RIFF asset, assuming IMAGE. Declare asset type explicitly to avoid this.") + RiveLog.w(TAG, "Unknown RIFF asset, assuming IMAGE. Declare asset type explicitly to avoid this.") AssetType.IMAGE } else -> { - Log.w(TAG, "Could not infer asset type from magic bytes, assuming IMAGE. Declare asset type explicitly to avoid this.") + RiveLog.w(TAG, "Could not infer asset type from magic bytes, assuming IMAGE. Declare asset type explicitly to avoid this.") AssetType.IMAGE } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt index 4b30374b..18a52ca2 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.Artboard import app.rive.RiveFile @@ -26,11 +25,12 @@ class HybridRiveFile( // Deprecated: Use getViewModelNamesAsync instead override val viewModelCount: Double? get() { + DeprecationWarning.warn("viewModelCount", "getViewModelNamesAsync") val file = riveFile ?: return null return try { runBlocking { file.getViewModelNames() }.size.toDouble() } catch (e: Exception) { - Log.e(TAG, "viewModelCount failed", e) + RiveLog.e(TAG, "viewModelCount failed: ${e.message}") null } } @@ -44,6 +44,7 @@ class HybridRiveFile( // Deprecated: Use getViewModelNamesAsync + viewModelByNameAsync instead override fun viewModelByIndex(index: Double): HybridViewModelSpec? { + DeprecationWarning.warn("viewModelByIndex", "getViewModelNamesAsync + viewModelByNameAsync") val file = riveFile ?: return null return try { val names = runBlocking { file.getViewModelNames() } @@ -51,7 +52,7 @@ class HybridRiveFile( if (idx < 0 || idx >= names.size) return null HybridViewModel(file, riveWorker, names[idx], this, ViewModelSource.Named(names[idx])) } catch (e: Exception) { - Log.e(TAG, "viewModelByIndex($index) failed", e) + RiveLog.e(TAG, "viewModelByIndex($index) failed: ${e.message}") null } } @@ -67,10 +68,11 @@ class HybridRiveFile( // Deprecated: Use viewModelByNameAsync instead override fun viewModelByName(name: String): HybridViewModelSpec? { + DeprecationWarning.warn("viewModelByName", "viewModelByNameAsync") return try { runBlocking { viewModelByNameImpl(name, validate = true) } } catch (e: Exception) { - Log.e(TAG, "viewModelByName('$name') failed", e) + RiveLog.e(TAG, "viewModelByName('$name') failed: ${e.message}") null } } @@ -105,10 +107,11 @@ class HybridRiveFile( // Deprecated: Use defaultArtboardViewModelAsync instead override fun defaultArtboardViewModel(artboardBy: ArtboardBy?): HybridViewModelSpec? { + DeprecationWarning.warn("defaultArtboardViewModel", "defaultArtboardViewModelAsync") return try { runBlocking { defaultArtboardViewModelImpl(artboardBy) } } catch (e: Exception) { - Log.e(TAG, "defaultArtboardViewModel failed", e) + RiveLog.e(TAG, "defaultArtboardViewModel failed: ${e.message}") null } } @@ -120,11 +123,12 @@ class HybridRiveFile( // Deprecated: Use getArtboardCountAsync instead override val artboardCount: Double get() { + DeprecationWarning.warn("artboardCount", "getArtboardCountAsync") val file = riveFile ?: return 0.0 return try { runBlocking { file.getArtboardNames() }.size.toDouble() } catch (e: Exception) { - Log.e(TAG, "artboardCount failed", e) + RiveLog.e(TAG, "artboardCount failed: ${e.message}") 0.0 } } @@ -139,11 +143,12 @@ class HybridRiveFile( // Deprecated: Use getArtboardNamesAsync instead override val artboardNames: Array get() { + DeprecationWarning.warn("artboardNames", "getArtboardNamesAsync") val file = riveFile ?: return emptyArray() return try { runBlocking { file.getArtboardNames() }.toTypedArray() } catch (e: Exception) { - Log.e(TAG, "artboardNames failed", e) + RiveLog.e(TAG, "artboardNames failed: ${e.message}") emptyArray() } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt index 5b95ab5a..22ce6447 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt @@ -16,12 +16,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext /** - * Custom RiveLog logger that logs to Logcat and broadcasts error messages - * to registered listeners. This captures C++ errors from the Rive CommandQueue - * (e.g., "State machine not found", "Draw failed") that are otherwise silent. + * Custom RiveLog logger that routes all Rive C++ runtime logs through [RiveLog] + * and broadcasts error messages to registered listeners. This captures C++ errors + * from the Rive CommandQueue (e.g., "State machine not found", "Draw failed"). */ object RiveErrorLogger : app.rive.RiveLog.Logger { - private val logcat = app.rive.RiveLog.LogcatLogger() private val listeners = mutableListOf<(String) -> Unit>() private val reportedErrors = mutableSetOf() @@ -47,13 +46,21 @@ object RiveErrorLogger : app.rive.RiveLog.Logger { synchronized(reportedErrors) { reportedErrors.clear() } } - override fun v(tag: String, msg: () -> String) = logcat.v(tag, msg) - override fun d(tag: String, msg: () -> String) = logcat.d(tag, msg) - override fun i(tag: String, msg: () -> String) = logcat.i(tag, msg) - override fun w(tag: String, msg: () -> String) = logcat.w(tag, msg) + override fun v(tag: String, msg: () -> String) { + RiveLog.d(tag, msg()) + } + override fun d(tag: String, msg: () -> String) { + RiveLog.d(tag, msg()) + } + override fun i(tag: String, msg: () -> String) { + RiveLog.i(tag, msg()) + } + override fun w(tag: String, msg: () -> String) { + RiveLog.w(tag, msg()) + } override fun e(tag: String, t: Throwable?, msg: () -> String) { val message = msg() - logcat.e(tag, t) { message } + RiveLog.e(tag, message) broadcastError(tag, message) } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt index b73f6772..1488f34c 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.RiveFile import app.rive.ViewModelInstance @@ -33,22 +32,24 @@ class HybridViewModel( override val propertyCount: Double get() { + DeprecationWarning.warn("propertyCount", "getPropertyCountAsync") val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR) return try { runBlocking { riveFile.getViewModelProperties(name) }.size.toDouble() } catch (e: Exception) { - Log.e(TAG, "propertyCount failed", e) + RiveLog.e(TAG, "propertyCount failed: ${e.message}") 0.0 } } override val instanceCount: Double get() { + DeprecationWarning.warn("instanceCount", "getInstanceCountAsync") val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR) return try { runBlocking { riveFile.getViewModelInstanceNames(name) }.size.toDouble() } catch (e: Exception) { - Log.e(TAG, "instanceCount failed", e) + RiveLog.e(TAG, "instanceCount failed: ${e.message}") 0.0 } } @@ -68,6 +69,7 @@ class HybridViewModel( // Deprecated: Use createInstanceByNameAsync instead override fun createInstanceByIndex(index: Double): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("createInstanceByIndex", "createInstanceByNameAsync") val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR) return try { val idx = index.toInt() @@ -78,7 +80,7 @@ class HybridViewModel( } catch (e: UnsupportedOperationException) { throw e } catch (e: Exception) { - Log.e(TAG, "createInstanceByIndex($index) failed", e) + RiveLog.e(TAG, "createInstanceByIndex($index) failed: ${e.message}") null } } @@ -94,13 +96,14 @@ class HybridViewModel( // Deprecated: Use createInstanceByNameAsync instead override fun createInstanceByName(name: String): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("createInstanceByName", "createInstanceByNameAsync") if (viewModelName == null) throw UnsupportedOperationException(NO_NAME_ERROR) return try { runBlocking { createInstanceByNameImpl(name) } } catch (e: UnsupportedOperationException) { throw e } catch (e: Exception) { - Log.e(TAG, "createInstanceByName('$name') failed", e) + RiveLog.e(TAG, "createInstanceByName('$name') failed: ${e.message}") null } } @@ -112,12 +115,13 @@ class HybridViewModel( // Deprecated: Use createDefaultInstanceAsync instead override fun createDefaultInstance(): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("createDefaultInstance", "createDefaultInstanceAsync") return try { val source = vmSource.defaultInstance() val vmi = ViewModelInstance.fromFile(riveFile, source) HybridViewModelInstance(vmi, riveWorker, parentFile, viewModelName) } catch (e: Exception) { - Log.e(TAG, "createDefaultInstance failed", e) + RiveLog.e(TAG, "createDefaultInstance failed: ${e.message}") null } } @@ -132,12 +136,13 @@ class HybridViewModel( // Deprecated: Use createBlankInstanceAsync instead override fun createInstance(): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("createInstance", "createBlankInstanceAsync") return try { val source = vmSource.blankInstance() val vmi = ViewModelInstance.fromFile(riveFile, source) HybridViewModelInstance(vmi, riveWorker, parentFile, viewModelName) } catch (e: Exception) { - Log.e(TAG, "createInstance (blank) failed", e) + RiveLog.e(TAG, "createInstance (blank) failed: ${e.message}") null } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt index 492bef37..50c896a1 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.ViewModelInstance import com.facebook.proguard.annotations.DoNotStrip @@ -22,10 +21,11 @@ class HybridViewModelBooleanProperty( // Deprecated: Use getValueAsync (read) or set(value) (write) instead override var value: Boolean get() { + DeprecationWarning.warn("BooleanProperty.value", "getValueAsync") return try { runBlocking { instance.getBooleanFlow(path).first() } } catch (e: Exception) { - Log.e(TAG, "getValue failed for path '$path'", e) + RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}") false } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt index b8a68f25..cfec0424 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.ViewModelInstance import com.facebook.proguard.annotations.DoNotStrip @@ -22,10 +21,11 @@ class HybridViewModelColorProperty( // Deprecated: Use getValueAsync (read) or set(value) (write) instead override var value: Double get() { + DeprecationWarning.warn("ColorProperty.value", "getValueAsync") return try { runBlocking { instance.getColorFlow(path).first() }.toDouble() } catch (e: Exception) { - Log.e(TAG, "getValue failed for path '$path'", e) + RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}") 0.0 } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt index fa7c4052..a6ba9eb1 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.ViewModelInstance import com.facebook.proguard.annotations.DoNotStrip @@ -22,10 +21,11 @@ class HybridViewModelEnumProperty( // Deprecated: Use getValueAsync (read) or set(value) (write) instead override var value: String get() { + DeprecationWarning.warn("EnumProperty.value", "getValueAsync") return try { runBlocking { instance.getEnumFlow(path).first() } } catch (e: Exception) { - Log.e(TAG, "getValue failed for path '$path'", e) + RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}") "" } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelInstance.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelInstance.kt index b9321556..98622bf8 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelInstance.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelInstance.kt @@ -141,10 +141,11 @@ class HybridViewModelInstance( // Deprecated: Use viewModelAsync instead override fun viewModel(path: String): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("viewModel", "viewModelAsync") return try { viewModelImpl(path) } catch (e: Exception) { - Log.e(TAG, "viewModel failed for path '$path'", e) + RiveLog.e(TAG, "viewModel failed for path '$path': ${e.message}") null } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt index ab819cba..da0afe43 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt @@ -25,10 +25,11 @@ class HybridViewModelListProperty( // Deprecated: Use getLengthAsync instead override val length: Double get() { + DeprecationWarning.warn("ListProperty.length", "getLengthAsync") return try { runBlocking { instance.getListSize(path) }.toDouble() } catch (e: Exception) { - Log.e(TAG, "getListSize failed for path '$path'", e) + RiveLog.e(TAG, "getListSize failed for path '$path': ${e.message}") 0.0 } } @@ -46,10 +47,11 @@ class HybridViewModelListProperty( // Deprecated: Use getInstanceAtAsync instead override fun getInstanceAt(index: Double): HybridViewModelInstanceSpec? { + DeprecationWarning.warn("ListProperty.getInstanceAt", "getInstanceAtAsync") return try { runBlocking { fetchInstanceAt(index) } } catch (e: Exception) { - Log.e(TAG, "getInstanceAt($index) failed for path '$path'", e) + RiveLog.e(TAG, "getInstanceAt($index) failed for path '$path': ${e.message}") null } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt index 48dbcf13..bca15999 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.ViewModelInstance import com.facebook.proguard.annotations.DoNotStrip @@ -22,10 +21,11 @@ class HybridViewModelNumberProperty( // Deprecated: Use getValueAsync (read) or set(value) (write) instead override var value: Double get() { + DeprecationWarning.warn("NumberProperty.value", "getValueAsync") return try { runBlocking { instance.getNumberFlow(path).first() }.toDouble() } catch (e: Exception) { - Log.e(TAG, "getValue failed for path '$path'", e) + RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}") 0.0 } } diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt index 4fabe12f..150101d4 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt @@ -1,6 +1,5 @@ package com.margelo.nitro.rive -import android.util.Log import androidx.annotation.Keep import app.rive.ViewModelInstance import com.facebook.proguard.annotations.DoNotStrip @@ -22,10 +21,11 @@ class HybridViewModelStringProperty( // Deprecated: Use getValueAsync (read) or set(value) (write) instead override var value: String get() { + DeprecationWarning.warn("StringProperty.value", "getValueAsync") return try { runBlocking { instance.getStringFlow(path).first() } } catch (e: Exception) { - Log.e(TAG, "getValue failed for path '$path'", e) + RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}") "" } } diff --git a/example/__tests__/rivelog.harness.tsx b/example/__tests__/rivelog.harness.tsx new file mode 100644 index 00000000..3da82c1a --- /dev/null +++ b/example/__tests__/rivelog.harness.tsx @@ -0,0 +1,98 @@ +import { describe, it, expect, waitFor, cleanup } from 'react-native-harness'; +import { RiveFileFactory, RiveLog } from '@rive-app/react-native'; + +const BOUNCING_BALL = require('../assets/rive/bouncing_ball.riv'); +const isExperimental = RiveFileFactory.getBackend() === 'experimental'; + +type LogEntry = { level: string; tag: string; message: string }; + +describe('RiveLog', () => { + // Deprecation warnings only fire in the experimental backend + (isExperimental ? it : it.skip)( + 'captures deprecation warning from sync method', + async () => { + const logs: LogEntry[] = []; + RiveLog.setHandler((level, tag, message) => { + logs.push({ level, tag, message }); + }); + + const file = await RiveFileFactory.fromSource(BOUNCING_BALL, undefined); + + file.defaultArtboardViewModel(); + + await waitFor( + () => { + const deprecation = logs.find((l) => l.tag === 'Deprecation'); + expect(deprecation).toBeDefined(); + expect(deprecation!.level).toBe('warn'); + expect(deprecation!.message).toContain('defaultArtboardViewModel'); + expect(deprecation!.message).toContain( + 'defaultArtboardViewModelAsync' + ); + }, + { timeout: 2000 } + ); + + RiveLog.resetHandler(); + cleanup(); + } + ); + + (isExperimental ? it : it.skip)( + 'emits each deprecation only once', + async () => { + const logs: LogEntry[] = []; + RiveLog.setHandler((level, tag, message) => { + logs.push({ level, tag, message }); + }); + + const file = await RiveFileFactory.fromSource(BOUNCING_BALL, undefined); + + // Use artboardNames — a different deprecated method so the once-per-session + // dedup doesn't collide with the previous test. + file.artboardNames; + file.artboardNames; + + await waitFor( + () => { + const deprecations = logs.filter( + (l) => + l.tag === 'Deprecation' && l.message.includes('artboardNames') + ); + expect(deprecations.length).toBe(1); + }, + { timeout: 2000 } + ); + + RiveLog.resetHandler(); + cleanup(); + } + ); + + it('suppresses all logs with a no-op handler', async () => { + const logs: LogEntry[] = []; + RiveLog.setHandler(() => {}); + + const file = await RiveFileFactory.fromSource(BOUNCING_BALL, undefined); + + file.artboardCount; + + expect(logs.length).toBe(0); + + RiveLog.resetHandler(); + cleanup(); + }); + + it('resetHandler restores default logging without throwing', async () => { + RiveLog.setHandler(() => {}); + RiveLog.resetHandler(); + + const file = await RiveFileFactory.fromSource(BOUNCING_BALL, undefined); + + file.viewModelByName('nonexistent'); + + expect(true).toBe(true); + + cleanup(); + }); +}); diff --git a/ios/DeprecationWarning.swift b/ios/DeprecationWarning.swift new file mode 100644 index 00000000..1b8d48f1 --- /dev/null +++ b/ios/DeprecationWarning.swift @@ -0,0 +1,10 @@ +enum DeprecationWarning { + private static var warned = Set() + + static func warn(_ method: String, replacement: String) { + guard !warned.contains(method) else { return } + warned.insert(method) + RiveLog.w("Deprecation", + "'\(method)' is deprecated and blocks the JS thread. Use '\(replacement)' instead.") + } +} diff --git a/ios/HybridRiveLogger.swift b/ios/HybridRiveLogger.swift new file mode 100644 index 00000000..7ae96295 --- /dev/null +++ b/ios/HybridRiveLogger.swift @@ -0,0 +1,11 @@ +import NitroModules + +class HybridRiveLogger: HybridRiveLoggerSpec { + func setHandler(handler: @escaping (String, String, String) -> Void) throws { + RiveLog.handler = handler + } + + func resetHandler() throws { + RiveLog.handler = nil + } +} diff --git a/ios/RiveLog.swift b/ios/RiveLog.swift new file mode 100644 index 00000000..2f724c75 --- /dev/null +++ b/ios/RiveLog.swift @@ -0,0 +1,35 @@ +enum RiveLog { + static var handler: ((String, String, String) -> Void)? + + static func e(_ tag: String, _ message: String) { + if let handler = handler { + handler("error", tag, message) + } else { + RCTLogError("[\(tag)] \(message)") + } + } + + static func w(_ tag: String, _ message: String) { + if let handler = handler { + handler("warn", tag, message) + } else { + RCTLogWarn("[\(tag)] \(message)") + } + } + + static func i(_ tag: String, _ message: String) { + if let handler = handler { + handler("info", tag, message) + } else { + RCTLogInfo("[\(tag)] \(message)") + } + } + + static func d(_ tag: String, _ message: String) { + if let handler = handler { + handler("debug", tag, message) + } else { + RCTLog("[\(tag)] \(message)") + } + } +} diff --git a/ios/new/HybridRiveFile.swift b/ios/new/HybridRiveFile.swift index d6865c31..b8ee3c69 100644 --- a/ios/new/HybridRiveFile.swift +++ b/ios/new/HybridRiveFile.swift @@ -16,12 +16,13 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use getViewModelNamesAsync instead var viewModelCount: Double? { + DeprecationWarning.warn("viewModelCount", replacement: "getViewModelNamesAsync") guard let file = file else { return nil } do { let names = try blockingAsync { try await file.getViewModelNames() } return Double(names.count) } catch { - RCTLogError("[RiveFile] viewModelCount failed: \(error)") + RiveLog.e("RiveFile", "viewModelCount failed: \(error)") return nil } } @@ -35,6 +36,7 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use getViewModelNamesAsync + viewModelByNameAsync instead func viewModelByIndex(index: Double) throws -> (any HybridViewModelSpec)? { + DeprecationWarning.warn("viewModelByIndex", replacement: "getViewModelNamesAsync + viewModelByNameAsync") guard let file = file, let worker = worker else { return nil } return try blockingAsync { let names = try await file.getViewModelNames() @@ -55,6 +57,7 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use viewModelByNameAsync instead func viewModelByName(name: String) throws -> (any HybridViewModelSpec)? { + DeprecationWarning.warn("viewModelByName", replacement: "viewModelByNameAsync") return try blockingAsync { try await self.viewModelByNameImpl(name: name, validate: true) } } @@ -90,6 +93,7 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use defaultArtboardViewModelAsync instead func defaultArtboardViewModel(artboardBy: ArtboardBy?) throws -> (any HybridViewModelSpec)? { + DeprecationWarning.warn("defaultArtboardViewModel", replacement: "defaultArtboardViewModelAsync") return try blockingAsync { try await self.defaultArtboardViewModelImpl(artboardBy: artboardBy) } } @@ -99,12 +103,13 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use getArtboardCountAsync instead var artboardCount: Double { + DeprecationWarning.warn("artboardCount", replacement: "getArtboardCountAsync") guard let file = file else { return 0 } do { let names = try blockingAsync { try await file.getArtboardNames() } return Double(names.count) } catch { - RCTLogError("[RiveFile] artboardCount failed: \(error)") + RiveLog.e("RiveFile", "artboardCount failed: \(error)") return 0 } } @@ -119,11 +124,12 @@ class HybridRiveFile: HybridRiveFileSpec { // Deprecated: Use getArtboardNamesAsync instead var artboardNames: [String] { + DeprecationWarning.warn("artboardNames", replacement: "getArtboardNamesAsync") guard let file = file else { return [] } do { return try blockingAsync { try await file.getArtboardNames() } } catch { - RCTLogError("[RiveFile] artboardNames failed: \(error)") + RiveLog.e("RiveFile", "artboardNames failed: \(error)") return [] } } diff --git a/ios/new/HybridRiveFileFactory.swift b/ios/new/HybridRiveFileFactory.swift index 5cf89f54..f7f2751c 100644 --- a/ios/new/HybridRiveFileFactory.swift +++ b/ios/new/HybridRiveFileFactory.swift @@ -6,7 +6,12 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl // All files must share the same Worker so artboard handles are valid across files // (each Worker has its own C++ command server with its own m_artboards map) - private static let sharedWorkerTask = Task { @MainActor in try await Worker() } + private static let sharedWorkerTask = Task { @MainActor in + if !(RiveRuntime.RiveLog.logger is RiveRuntimeLogger) { + RiveRuntime.RiveLog.logger = RiveRuntimeLogger() + } + return try await Worker() + } func fromURL(url: String, loadCdn: Bool, referencedAssets: ReferencedAssetsType?) throws -> Promise<(any HybridRiveFileSpec)> diff --git a/ios/new/HybridViewModel.swift b/ios/new/HybridViewModel.swift index e0905931..3c710c00 100644 --- a/ios/new/HybridViewModel.swift +++ b/ios/new/HybridViewModel.swift @@ -15,19 +15,21 @@ class HybridViewModel: HybridViewModelSpec { var modelName: String { vmName } var propertyCount: Double { + DeprecationWarning.warn("propertyCount", replacement: "getPropertyCountAsync") do { return Double(try blockingAsync { try await self.file.getProperties(of: self.vmName) }.count) } catch { - RCTLogError("[HybridViewModel] propertyCount failed: \(error)") + RiveLog.e("ViewModel", "propertyCount failed: \(error)") return 0 } } var instanceCount: Double { + DeprecationWarning.warn("instanceCount", replacement: "getInstanceCountAsync") do { return Double(try blockingAsync { try await self.file.getInstanceNames(of: self.vmName) }.count) } catch { - RCTLogError("[HybridViewModel] instanceCount failed: \(error)") + RiveLog.e("ViewModel", "instanceCount failed: \(error)") return 0 } } @@ -60,6 +62,7 @@ class HybridViewModel: HybridViewModelSpec { // Deprecated: Use createInstanceByNameAsync instead func createInstanceByIndex(index: Double) throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("createInstanceByIndex", replacement: "createInstanceByNameAsync") return try blockingAsync { try await self.createInstanceByIndexImpl(index: index) } } @@ -70,6 +73,7 @@ class HybridViewModel: HybridViewModelSpec { // Deprecated: Use createInstanceByNameAsync instead func createInstanceByName(name: String) throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("createInstanceByName", replacement: "createInstanceByNameAsync") return try blockingAsync { try await self.createInstanceByNameImpl(name: name) } } @@ -79,6 +83,7 @@ class HybridViewModel: HybridViewModelSpec { // Deprecated: Use createDefaultInstanceAsync instead func createDefaultInstance() throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("createDefaultInstance", replacement: "createDefaultInstanceAsync") return try blockingAsync { try await self.createDefaultInstanceImpl() } } @@ -93,6 +98,7 @@ class HybridViewModel: HybridViewModelSpec { // Deprecated: Use createBlankInstanceAsync instead func createInstance() throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("createInstance", replacement: "createBlankInstanceAsync") return try blockingAsync { try await self.createInstanceImpl() } } diff --git a/ios/new/HybridViewModelBooleanProperty.swift b/ios/new/HybridViewModelBooleanProperty.swift index 0462683a..ce1d33e7 100644 --- a/ios/new/HybridViewModelBooleanProperty.swift +++ b/ios/new/HybridViewModelBooleanProperty.swift @@ -15,10 +15,11 @@ class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec { // Deprecated: Use getValueAsync (read) or set(value:) (write) instead var value: Bool { get { + DeprecationWarning.warn("BooleanProperty.value", replacement: "getValueAsync") do { return try blockingAsync { try await self.instance.value(of: self.prop) } } catch { - RCTLogError("[BooleanProperty] getValue failed: \(error)") + RiveLog.e("BooleanProperty", "getValue failed: \(error)") return false } } diff --git a/ios/new/HybridViewModelColorProperty.swift b/ios/new/HybridViewModelColorProperty.swift index 455e7768..251dedfb 100644 --- a/ios/new/HybridViewModelColorProperty.swift +++ b/ios/new/HybridViewModelColorProperty.swift @@ -20,10 +20,11 @@ class HybridViewModelColorProperty: HybridViewModelColorPropertySpec { // Deprecated: Use getValueAsync (read) or set(value:) (write) instead var value: Double { get { + DeprecationWarning.warn("ColorProperty.value", replacement: "getValueAsync") do { return try blockingAsync { try await self.fetchColorValue() } } catch { - RCTLogError("[ColorProperty] getValue failed: \(error)") + RiveLog.e("ColorProperty", "getValue failed: \(error)") return 0 } } diff --git a/ios/new/HybridViewModelEnumProperty.swift b/ios/new/HybridViewModelEnumProperty.swift index 74db80c6..b2151ebb 100644 --- a/ios/new/HybridViewModelEnumProperty.swift +++ b/ios/new/HybridViewModelEnumProperty.swift @@ -15,10 +15,11 @@ class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec { // Deprecated: Use getValueAsync (read) or set(value:) (write) instead var value: String { get { + DeprecationWarning.warn("EnumProperty.value", replacement: "getValueAsync") do { return try blockingAsync { try await self.instance.value(of: self.prop) } } catch { - RCTLogError("[EnumProperty] getValue failed: \(error)") + RiveLog.e("EnumProperty", "getValue failed: \(error)") return "" } } diff --git a/ios/new/HybridViewModelInstance.swift b/ios/new/HybridViewModelInstance.swift index 66d2b7e9..bf85f999 100644 --- a/ios/new/HybridViewModelInstance.swift +++ b/ios/new/HybridViewModelInstance.swift @@ -62,13 +62,14 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec { let vmi = try await self.viewModelInstance.value(of: prop) return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker) } catch { - RCTLogError("[ViewModelInstance] viewModel(path: '\(path)') failed: \(error)") + RiveLog.e("ViewModelInstance", "viewModel(path: '\(path)') failed: \(error)") return nil } } // Deprecated: Use viewModelAsync instead func viewModel(path: String) throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("viewModel", replacement: "viewModelAsync") return try blockingAsync { try await self.viewModelImpl(path: path) } } diff --git a/ios/new/HybridViewModelListProperty.swift b/ios/new/HybridViewModelListProperty.swift index 09d71524..1819f6fc 100644 --- a/ios/new/HybridViewModelListProperty.swift +++ b/ios/new/HybridViewModelListProperty.swift @@ -18,12 +18,13 @@ class HybridViewModelListProperty: HybridViewModelListPropertySpec { // Deprecated: Use getLengthAsync instead var length: Double { + DeprecationWarning.warn("ListProperty.length", replacement: "getLengthAsync") do { return try blockingAsync { try await Double(self.vmiInstance.size(of: self.prop)) } } catch { - RCTLogError("[ListProperty] length failed: \(error)") + RiveLog.e("ListProperty", "length failed: \(error)") return 0 } } @@ -43,6 +44,7 @@ class HybridViewModelListProperty: HybridViewModelListPropertySpec { // Deprecated: Use getInstanceAtAsync instead func getInstanceAt(index: Double) throws -> (any HybridViewModelInstanceSpec)? { + DeprecationWarning.warn("ListProperty.getInstanceAt", replacement: "getInstanceAtAsync") return try blockingAsync { try await self.fetchInstance(at: index) } } diff --git a/ios/new/HybridViewModelNumberProperty.swift b/ios/new/HybridViewModelNumberProperty.swift index 4a986434..75412ce0 100644 --- a/ios/new/HybridViewModelNumberProperty.swift +++ b/ios/new/HybridViewModelNumberProperty.swift @@ -15,10 +15,11 @@ class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec { // Deprecated: Use getValueAsync (read) or set(value:) (write) instead var value: Double { get { + DeprecationWarning.warn("NumberProperty.value", replacement: "getValueAsync") do { return try blockingAsync { try await Double(self.instance.value(of: self.prop)) } } catch { - RCTLogError("[NumberProperty] getValue failed: \(error)") + RiveLog.e("NumberProperty", "getValue failed: \(error)") return 0 } } diff --git a/ios/new/HybridViewModelStringProperty.swift b/ios/new/HybridViewModelStringProperty.swift index e65e364c..73d902a8 100644 --- a/ios/new/HybridViewModelStringProperty.swift +++ b/ios/new/HybridViewModelStringProperty.swift @@ -15,10 +15,11 @@ class HybridViewModelStringProperty: HybridViewModelStringPropertySpec { // Deprecated: Use getValueAsync (read) or set(value:) (write) instead var value: String { get { + DeprecationWarning.warn("StringProperty.value", replacement: "getValueAsync") do { return try blockingAsync { try await self.instance.value(of: self.prop) } } catch { - RCTLogError("[StringProperty] getValue failed: \(error)") + RiveLog.e("StringProperty", "getValue failed: \(error)") return "" } } diff --git a/ios/new/RiveRuntimeLogger.swift b/ios/new/RiveRuntimeLogger.swift new file mode 100644 index 00000000..3ed6aea7 --- /dev/null +++ b/ios/new/RiveRuntimeLogger.swift @@ -0,0 +1,56 @@ +@_spi(RiveExperimental) import RiveRuntime + +private func tagName(_ tag: RiveRuntime.RiveLog.Tag) -> String { + switch tag { + case .rive: return "Rive" + case .worker: return "Worker" + case .file: return "File" + case .artboard: return "Artboard" + case .stateMachine: return "StateMachine" + case .viewModelInstance: return "ViewModelInstance" + case .image: return "Image" + case .font: return "Font" + case .audio: return "Audio" + case .view: return "RiveUIView" + case .custom(let name): return name + @unknown default: return "Unknown" + } +} + +/// Implements the Rive iOS SDK's `RiveLog.Logger` protocol and forwards all +/// C++ runtime logs through our bridge-level `RiveLog` utility, giving JS +/// visibility into file, artboard, state machine, and view model diagnostics. +final class RiveRuntimeLogger: RiveRuntime.RiveLog.Logger, @unchecked Sendable { + func notice(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.i(tagName(tag), message()) + } + + func debug(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.d(tagName(tag), message()) + } + + func trace(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.d(tagName(tag), message()) + } + + func info(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.i(tagName(tag), message()) + } + + func error(tag: RiveRuntime.RiveLog.Tag, error: (any Error)?, _ message: @escaping () -> String) { + let suffix = error.map { " (\($0.localizedDescription))" } ?? "" + RiveLog.e(tagName(tag), "\(message())\(suffix)") + } + + func warning(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.w(tagName(tag), message()) + } + + func fault(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.e(tagName(tag), message()) + } + + func critical(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.e(tagName(tag), message()) + } +} diff --git a/nitro.json b/nitro.json index e1e89ae8..2eef00ab 100644 --- a/nitro.json +++ b/nitro.json @@ -32,6 +32,10 @@ "RiveRuntime": { "swift": "HybridRiveRuntime", "kotlin": "HybridRiveRuntime" + }, + "RiveLogger": { + "swift": "HybridRiveLogger", + "kotlin": "HybridRiveLogger" } }, "ignorePaths": ["node_modules"] diff --git a/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_std__string.hpp b/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_std__string.hpp new file mode 100644 index 00000000..deb2cab1 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_std__string.hpp @@ -0,0 +1,76 @@ +/// +/// JFunc_void_std__string_std__string_std__string.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include +#include +#include + +namespace margelo::nitro::rive { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(level: String, tag: String, message: String) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_std__string_std__string_std__string: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/Func_void_std__string_std__string_std__string;"; + + public: + /** + * Invokes the function this `JFunc_void_std__string_std__string_std__string` instance holds through JNI. + */ + void invoke(const std::string& level, const std::string& tag, const std::string& message) const { + static const auto method = javaClassStatic()->getMethod /* level */, jni::alias_ref /* tag */, jni::alias_ref /* message */)>("invoke"); + method(self(), jni::make_jstring(level), jni::make_jstring(tag), jni::make_jstring(message)); + } + }; + + /** + * An implementation of Func_void_std__string_std__string_std__string that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_void_std__string_std__string_std__string_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_std__string_std__string_std__string_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_std__string_std__string_std__string_cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref level, jni::alias_ref tag, jni::alias_ref message) { + _func(level->toStdString(), tag->toStdString(), message->toStdString()); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/Func_void_std__string_std__string_std__string_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_std__string_std__string_std__string_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_std__string_std__string_std__string_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.cpp b/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.cpp new file mode 100644 index 00000000..9ccf9036 --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.cpp @@ -0,0 +1,59 @@ +/// +/// JHybridRiveLoggerSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridRiveLoggerSpec.hpp" + + + +#include +#include +#include "JFunc_void_std__string_std__string_std__string.hpp" +#include + +namespace margelo::nitro::rive { + + std::shared_ptr JHybridRiveLoggerSpec::JavaPart::getJHybridRiveLoggerSpec() { + auto hybridObject = JHybridObject::JavaPart::getJHybridObject(); + auto castHybridObject = std::dynamic_pointer_cast(hybridObject); + if (castHybridObject == nullptr) [[unlikely]] { + throw std::runtime_error("Failed to downcast JHybridObject to JHybridRiveLoggerSpec!"); + } + return castHybridObject; + } + + jni::local_ref JHybridRiveLoggerSpec::CxxPart::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + std::shared_ptr JHybridRiveLoggerSpec::CxxPart::createHybridObject(const jni::local_ref& javaPart) { + auto castJavaPart = jni::dynamic_ref_cast(javaPart); + if (castJavaPart == nullptr) [[unlikely]] { + throw std::runtime_error("Failed to cast JHybridObject::JavaPart to JHybridRiveLoggerSpec::JavaPart!"); + } + return std::make_shared(castJavaPart); + } + + void JHybridRiveLoggerSpec::CxxPart::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridRiveLoggerSpec::CxxPart::initHybrid), + }); + } + + // Properties + + + // Methods + void JHybridRiveLoggerSpec::setHandler(const std::function& handler) { + static const auto method = _javaPart->javaClassStatic()->getMethod /* handler */)>("setHandler_cxx"); + method(_javaPart, JFunc_void_std__string_std__string_std__string_cxx::fromCpp(handler)); + } + void JHybridRiveLoggerSpec::resetHandler() { + static const auto method = _javaPart->javaClassStatic()->getMethod("resetHandler"); + method(_javaPart); + } + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.hpp b/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.hpp new file mode 100644 index 00000000..2dceec70 --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridRiveLoggerSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridRiveLoggerSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridRiveLoggerSpec.hpp" + + + + +namespace margelo::nitro::rive { + + using namespace facebook; + + class JHybridRiveLoggerSpec: public virtual HybridRiveLoggerSpec, public virtual JHybridObject { + public: + struct JavaPart: public jni::JavaClass { + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/HybridRiveLoggerSpec;"; + std::shared_ptr getJHybridRiveLoggerSpec(); + }; + struct CxxPart: public jni::HybridClass { + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/HybridRiveLoggerSpec$CxxPart;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + using HybridBase::HybridBase; + protected: + std::shared_ptr createHybridObject(const jni::local_ref& javaPart) override; + }; + + public: + explicit JHybridRiveLoggerSpec(const jni::local_ref& javaPart): + HybridObject(HybridRiveLoggerSpec::TAG), + JHybridObject(javaPart), + _javaPart(jni::make_global(javaPart)) {} + ~JHybridRiveLoggerSpec() override { + // Hermes GC can destroy JS objects on a non-JNI Thread. + jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); + } + + public: + inline const jni::global_ref& getJavaPart() const noexcept { + return _javaPart; + } + + public: + // Properties + + + public: + // Methods + void setHandler(const std::function& handler) override; + void resetHandler() override; + + private: + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_std__string_std__string_std__string.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_std__string_std__string_std__string.kt new file mode 100644 index 00000000..20a3dd14 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_std__string_std__string_std__string.kt @@ -0,0 +1,80 @@ +/// +/// Func_void_std__string_std__string_std__string.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(level: string, tag: string, message: string) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_std__string_std__string_std__string: (String, String, String) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(level: String, tag: String, message: String): Unit +} + +/** + * Represents the JavaScript callback `(level: string, tag: string, message: string) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_std__string_std__string_std__string_cxx: Func_void_std__string_std__string_std__string { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(level: String, tag: String, message: String): Unit + = invoke_cxx(level,tag,message) + + @FastNative + private external fun invoke_cxx(level: String, tag: String, message: String): Unit +} + +/** + * Represents the JavaScript callback `(level: string, tag: string, message: string) => void`. + * This is implemented in Java/Kotlin, via a `(String, String, String) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_std__string_std__string_std__string_java(private val function: (String, String, String) -> Unit): Func_void_std__string_std__string_std__string { + @DoNotStrip + @Keep + override fun invoke(level: String, tag: String, message: String): Unit { + return this.function(level, tag, message) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveLoggerSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveLoggerSpec.kt new file mode 100644 index 00000000..e3a31bcc --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveLoggerSpec.kt @@ -0,0 +1,63 @@ +/// +/// HybridRiveLoggerSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.rive + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.HybridObject + +/** + * A Kotlin class representing the RiveLogger HybridObject. + * Implement this abstract class to create Kotlin-based instances of RiveLogger. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridRiveLoggerSpec: HybridObject() { + // Properties + + + // Methods + abstract fun setHandler(handler: (level: String, tag: String, message: String) -> Unit): Unit + + @DoNotStrip + @Keep + private fun setHandler_cxx(handler: Func_void_std__string_std__string_std__string): Unit { + val __result = setHandler(handler) + return __result + } + + @DoNotStrip + @Keep + abstract fun resetHandler(): Unit + + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject RiveLogger]" + } + + // C++ backing class + @DoNotStrip + @Keep + protected open class CxxPart(javaPart: HybridRiveLoggerSpec): HybridObject.CxxPart(javaPart) { + // C++ JHybridRiveLoggerSpec::CxxPart::initHybrid(...) + external override fun initHybrid(): HybridData + } + override fun createCxxPart(): CxxPart { + return CxxPart(this) + } + + companion object { + protected const val TAG = "HybridRiveLoggerSpec" + } +} diff --git a/nitrogen/generated/android/rive+autolinking.cmake b/nitrogen/generated/android/rive+autolinking.cmake index 020c674e..ffe01178 100644 --- a/nitrogen/generated/android/rive+autolinking.cmake +++ b/nitrogen/generated/android/rive+autolinking.cmake @@ -40,6 +40,7 @@ target_sources( ../nitrogen/generated/shared/c++/HybridRiveFontConfigSpec.cpp ../nitrogen/generated/shared/c++/HybridRiveImageSpec.cpp ../nitrogen/generated/shared/c++/HybridRiveImageFactorySpec.cpp + ../nitrogen/generated/shared/c++/HybridRiveLoggerSpec.cpp ../nitrogen/generated/shared/c++/HybridRiveRuntimeSpec.cpp ../nitrogen/generated/shared/c++/HybridRiveViewSpec.cpp ../nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp @@ -63,6 +64,7 @@ target_sources( ../nitrogen/generated/android/c++/JHybridRiveFontConfigSpec.cpp ../nitrogen/generated/android/c++/JHybridRiveImageSpec.cpp ../nitrogen/generated/android/c++/JHybridRiveImageFactorySpec.cpp + ../nitrogen/generated/android/c++/JHybridRiveLoggerSpec.cpp ../nitrogen/generated/android/c++/JHybridRiveRuntimeSpec.cpp ../nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp ../nitrogen/generated/android/c++/JVariant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.cpp diff --git a/nitrogen/generated/android/riveOnLoad.cpp b/nitrogen/generated/android/riveOnLoad.cpp index b2205fe9..e98e5b0c 100644 --- a/nitrogen/generated/android/riveOnLoad.cpp +++ b/nitrogen/generated/android/riveOnLoad.cpp @@ -22,6 +22,8 @@ #include "JHybridRiveFontConfigSpec.hpp" #include "JHybridRiveImageSpec.hpp" #include "JHybridRiveImageFactorySpec.hpp" +#include "JHybridRiveLoggerSpec.hpp" +#include "JFunc_void_std__string_std__string_std__string.hpp" #include "JHybridRiveRuntimeSpec.hpp" #include "JHybridRiveViewSpec.hpp" #include "JFunc_void_RiveError.hpp" @@ -101,6 +103,14 @@ struct JHybridRiveRuntimeSpecImpl: public jni::JavaClassgetJHybridRiveRuntimeSpec(); } }; +struct JHybridRiveLoggerSpecImpl: public jni::JavaClass { + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/rive/HybridRiveLogger;"; + static std::shared_ptr create() { + static auto constructorFn = javaClassStatic()->getConstructor(); + jni::local_ref javaPart = javaClassStatic()->newObject(constructorFn); + return javaPart->getJHybridRiveLoggerSpec(); + } +}; void registerAllNatives() { using namespace margelo::nitro; @@ -114,6 +124,8 @@ void registerAllNatives() { margelo::nitro::rive::JHybridRiveFontConfigSpec::CxxPart::registerNatives(); margelo::nitro::rive::JHybridRiveImageSpec::CxxPart::registerNatives(); margelo::nitro::rive::JHybridRiveImageFactorySpec::CxxPart::registerNatives(); + margelo::nitro::rive::JHybridRiveLoggerSpec::CxxPart::registerNatives(); + margelo::nitro::rive::JFunc_void_std__string_std__string_std__string_cxx::registerNatives(); margelo::nitro::rive::JHybridRiveRuntimeSpec::CxxPart::registerNatives(); margelo::nitro::rive::JHybridRiveViewSpec::CxxPart::registerNatives(); margelo::nitro::rive::JFunc_void_RiveError_cxx::registerNatives(); @@ -173,6 +185,12 @@ void registerAllNatives() { return JHybridRiveRuntimeSpecImpl::create(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "RiveLogger", + []() -> std::shared_ptr { + return JHybridRiveLoggerSpecImpl::create(); + } + ); } } // namespace margelo::nitro::rive diff --git a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp index 33aa9a8b..414fce11 100644 --- a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp +++ b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp @@ -15,6 +15,7 @@ #include "HybridRiveFontConfigSpecSwift.hpp" #include "HybridRiveImageFactorySpecSwift.hpp" #include "HybridRiveImageSpecSwift.hpp" +#include "HybridRiveLoggerSpecSwift.hpp" #include "HybridRiveRuntimeSpecSwift.hpp" #include "HybridRiveViewSpecSwift.hpp" #include "HybridViewModelArtboardPropertySpecSwift.hpp" @@ -234,6 +235,30 @@ namespace margelo::nitro::rive::bridge::swift { return swiftPart.toUnsafe(); } + // pragma MARK: std::function + Func_void_std__string_std__string_std__string create_Func_void_std__string_std__string_std__string(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = RNRive::Func_void_std__string_std__string_std__string::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const std::string& level, const std::string& tag, const std::string& message) mutable -> void { + swiftClosure.call(level, tag, message); + }; + } + + // pragma MARK: std::shared_ptr + std::shared_ptr create_std__shared_ptr_HybridRiveLoggerSpec_(void* NON_NULL swiftUnsafePointer) noexcept { + RNRive::HybridRiveLoggerSpec_cxx swiftPart = RNRive::HybridRiveLoggerSpec_cxx::fromUnsafe(swiftUnsafePointer); + return std::make_shared(swiftPart); + } + void* NON_NULL get_std__shared_ptr_HybridRiveLoggerSpec_(std__shared_ptr_HybridRiveLoggerSpec_ cppType) { + std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); + #ifdef NITRO_DEBUG + if (swiftWrapper == nullptr) [[unlikely]] { + throw std::runtime_error("Class \"HybridRiveLoggerSpec\" is not implemented in Swift!"); + } + #endif + RNRive::HybridRiveLoggerSpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); + return swiftPart.toUnsafe(); + } + // pragma MARK: std::shared_ptr std::shared_ptr create_std__shared_ptr_HybridRiveRuntimeSpec_(void* NON_NULL swiftUnsafePointer) noexcept { RNRive::HybridRiveRuntimeSpec_cxx swiftPart = RNRive::HybridRiveRuntimeSpec_cxx::fromUnsafe(swiftUnsafePointer); diff --git a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp index 5c148249..64cd2eab 100644 --- a/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp @@ -34,6 +34,8 @@ namespace margelo::nitro::rive { class HybridRiveFontConfigSpec; } namespace margelo::nitro::rive { class HybridRiveImageFactorySpec; } // Forward declaration of `HybridRiveImageSpec` to properly resolve imports. namespace margelo::nitro::rive { class HybridRiveImageSpec; } +// Forward declaration of `HybridRiveLoggerSpec` to properly resolve imports. +namespace margelo::nitro::rive { class HybridRiveLoggerSpec; } // Forward declaration of `HybridRiveRuntimeSpec` to properly resolve imports. namespace margelo::nitro::rive { class HybridRiveRuntimeSpec; } // Forward declaration of `HybridRiveViewSpec` to properly resolve imports. @@ -94,6 +96,8 @@ namespace RNRive { class HybridRiveFontConfigSpec_cxx; } namespace RNRive { class HybridRiveImageFactorySpec_cxx; } // Forward declaration of `HybridRiveImageSpec_cxx` to properly resolve imports. namespace RNRive { class HybridRiveImageSpec_cxx; } +// Forward declaration of `HybridRiveLoggerSpec_cxx` to properly resolve imports. +namespace RNRive { class HybridRiveLoggerSpec_cxx; } // Forward declaration of `HybridRiveRuntimeSpec_cxx` to properly resolve imports. namespace RNRive { class HybridRiveRuntimeSpec_cxx; } // Forward declaration of `HybridRiveViewSpec_cxx` to properly resolve imports. @@ -137,6 +141,7 @@ namespace RNRive { class HybridViewModelTriggerPropertySpec_cxx; } #include "HybridRiveFontConfigSpec.hpp" #include "HybridRiveImageFactorySpec.hpp" #include "HybridRiveImageSpec.hpp" +#include "HybridRiveLoggerSpec.hpp" #include "HybridRiveRuntimeSpec.hpp" #include "HybridRiveViewSpec.hpp" #include "HybridViewModelArtboardPropertySpec.hpp" @@ -868,6 +873,40 @@ namespace margelo::nitro::rive::bridge::swift { return Result>>>::withError(error); } + // pragma MARK: std::function + /** + * Specialized version of `std::function`. + */ + using Func_void_std__string_std__string_std__string = std::function; + /** + * Wrapper class for a `std::function`, this can be used from Swift. + */ + class Func_void_std__string_std__string_std__string_Wrapper final { + public: + explicit Func_void_std__string_std__string_std__string_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + inline void call(std::string level, std::string tag, std::string message) const noexcept { + _function->operator()(level, tag, message); + } + private: + std::unique_ptr> _function; + } SWIFT_NONCOPYABLE; + Func_void_std__string_std__string_std__string create_Func_void_std__string_std__string_std__string(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_std__string_std__string_std__string_Wrapper wrap_Func_void_std__string_std__string_std__string(Func_void_std__string_std__string_std__string value) noexcept { + return Func_void_std__string_std__string_std__string_Wrapper(std::move(value)); + } + + // pragma MARK: std::shared_ptr + /** + * Specialized version of `std::shared_ptr`. + */ + using std__shared_ptr_HybridRiveLoggerSpec_ = std::shared_ptr; + std::shared_ptr create_std__shared_ptr_HybridRiveLoggerSpec_(void* NON_NULL swiftUnsafePointer) noexcept; + void* NON_NULL get_std__shared_ptr_HybridRiveLoggerSpec_(std__shared_ptr_HybridRiveLoggerSpec_ cppType); + + // pragma MARK: std::weak_ptr + using std__weak_ptr_HybridRiveLoggerSpec_ = std::weak_ptr; + inline std__weak_ptr_HybridRiveLoggerSpec_ weakify_std__shared_ptr_HybridRiveLoggerSpec_(const std::shared_ptr& strong) noexcept { return strong; } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp index 6240bcbe..520fe707 100644 --- a/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp @@ -34,6 +34,8 @@ namespace margelo::nitro::rive { class HybridRiveFontConfigSpec; } namespace margelo::nitro::rive { class HybridRiveImageFactorySpec; } // Forward declaration of `HybridRiveImageSpec` to properly resolve imports. namespace margelo::nitro::rive { class HybridRiveImageSpec; } +// Forward declaration of `HybridRiveLoggerSpec` to properly resolve imports. +namespace margelo::nitro::rive { class HybridRiveLoggerSpec; } // Forward declaration of `HybridRiveRuntimeSpec` to properly resolve imports. namespace margelo::nitro::rive { class HybridRiveRuntimeSpec; } // Forward declaration of `HybridRiveViewSpec` to properly resolve imports. @@ -93,6 +95,7 @@ namespace margelo::nitro::rive { struct UnifiedRiveEvent; } #include "HybridRiveFontConfigSpec.hpp" #include "HybridRiveImageFactorySpec.hpp" #include "HybridRiveImageSpec.hpp" +#include "HybridRiveLoggerSpec.hpp" #include "HybridRiveRuntimeSpec.hpp" #include "HybridRiveViewSpec.hpp" #include "HybridViewModelArtboardPropertySpec.hpp" @@ -151,6 +154,8 @@ namespace RNRive { class HybridRiveFontConfigSpec_cxx; } namespace RNRive { class HybridRiveImageFactorySpec_cxx; } // Forward declaration of `HybridRiveImageSpec_cxx` to properly resolve imports. namespace RNRive { class HybridRiveImageSpec_cxx; } +// Forward declaration of `HybridRiveLoggerSpec_cxx` to properly resolve imports. +namespace RNRive { class HybridRiveLoggerSpec_cxx; } // Forward declaration of `HybridRiveRuntimeSpec_cxx` to properly resolve imports. namespace RNRive { class HybridRiveRuntimeSpec_cxx; } // Forward declaration of `HybridRiveViewSpec_cxx` to properly resolve imports. diff --git a/nitrogen/generated/ios/RNRiveAutolinking.mm b/nitrogen/generated/ios/RNRiveAutolinking.mm index 263fbb58..e753f6dc 100644 --- a/nitrogen/generated/ios/RNRiveAutolinking.mm +++ b/nitrogen/generated/ios/RNRiveAutolinking.mm @@ -16,6 +16,7 @@ #include "HybridRiveViewSpecSwift.hpp" #include "HybridRiveImageFactorySpecSwift.hpp" #include "HybridRiveRuntimeSpecSwift.hpp" +#include "HybridRiveLoggerSpecSwift.hpp" @interface RNRiveAutolinking : NSObject @end @@ -68,6 +69,13 @@ + (void) load { return hybridObject; } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "RiveLogger", + []() -> std::shared_ptr { + std::shared_ptr hybridObject = RNRive::RNRiveAutolinking::createRiveLogger(); + return hybridObject; + } + ); } @end diff --git a/nitrogen/generated/ios/RNRiveAutolinking.swift b/nitrogen/generated/ios/RNRiveAutolinking.swift index 658b9bb4..bf370d97 100644 --- a/nitrogen/generated/ios/RNRiveAutolinking.swift +++ b/nitrogen/generated/ios/RNRiveAutolinking.swift @@ -83,4 +83,16 @@ public final class RNRiveAutolinking { public static func isRiveRuntimeRecyclable() -> Bool { return HybridRiveRuntime.self is any RecyclableView.Type } + + public static func createRiveLogger() -> bridge.std__shared_ptr_HybridRiveLoggerSpec_ { + let hybridObject = HybridRiveLogger() + return { () -> bridge.std__shared_ptr_HybridRiveLoggerSpec_ in + let __cxxWrapped = hybridObject.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }() + } + + public static func isRiveLoggerRecyclable() -> Bool { + return HybridRiveLogger.self is any RecyclableView.Type + } } diff --git a/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.cpp b/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.cpp new file mode 100644 index 00000000..e1c0749c --- /dev/null +++ b/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.cpp @@ -0,0 +1,11 @@ +/// +/// HybridRiveLoggerSpecSwift.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridRiveLoggerSpecSwift.hpp" + +namespace margelo::nitro::rive { +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.hpp new file mode 100644 index 00000000..5a0e2364 --- /dev/null +++ b/nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.hpp @@ -0,0 +1,87 @@ +/// +/// HybridRiveLoggerSpecSwift.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include "HybridRiveLoggerSpec.hpp" + +// Forward declaration of `HybridRiveLoggerSpec_cxx` to properly resolve imports. +namespace RNRive { class HybridRiveLoggerSpec_cxx; } + + + +#include +#include + +#include "RNRive-Swift-Cxx-Umbrella.hpp" + +namespace margelo::nitro::rive { + + /** + * The C++ part of HybridRiveLoggerSpec_cxx.swift. + * + * HybridRiveLoggerSpecSwift (C++) accesses HybridRiveLoggerSpec_cxx (Swift), and might + * contain some additional bridging code for C++ <> Swift interop. + * + * Since this obviously introduces an overhead, I hope at some point in + * the future, HybridRiveLoggerSpec_cxx can directly inherit from the C++ class HybridRiveLoggerSpec + * to simplify the whole structure and memory management. + */ + class HybridRiveLoggerSpecSwift: public virtual HybridRiveLoggerSpec { + public: + // Constructor from a Swift instance + explicit HybridRiveLoggerSpecSwift(const RNRive::HybridRiveLoggerSpec_cxx& swiftPart): + HybridObject(HybridRiveLoggerSpec::TAG), + _swiftPart(swiftPart) { } + + public: + // Get the Swift part + inline RNRive::HybridRiveLoggerSpec_cxx& getSwiftPart() noexcept { + return _swiftPart; + } + + public: + inline size_t getExternalMemorySize() noexcept override { + return _swiftPart.getMemorySize(); + } + bool equals(const std::shared_ptr& other) override { + if (auto otherCast = std::dynamic_pointer_cast(other)) { + return _swiftPart.equals(otherCast->_swiftPart); + } + return false; + } + void dispose() noexcept override { + _swiftPart.dispose(); + } + std::string toString() override { + return _swiftPart.toString(); + } + + public: + // Properties + + + public: + // Methods + inline void setHandler(const std::function& handler) override { + auto __result = _swiftPart.setHandler(handler); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } + inline void resetHandler() override { + auto __result = _swiftPart.resetHandler(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + } + + private: + RNRive::HybridRiveLoggerSpec_cxx _swiftPart; + }; + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/ios/swift/Func_void_std__string_std__string_std__string.swift b/nitrogen/generated/ios/swift/Func_void_std__string_std__string_std__string.swift new file mode 100644 index 00000000..bff62c9e --- /dev/null +++ b/nitrogen/generated/ios/swift/Func_void_std__string_std__string_std__string.swift @@ -0,0 +1,46 @@ +/// +/// Func_void_std__string_std__string_std__string.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Wraps a Swift `(_ level: String, _ tag: String, _ message: String) -> Void` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_void_std__string_std__string_std__string { + public typealias bridge = margelo.nitro.rive.bridge.swift + + private let closure: (_ level: String, _ tag: String, _ message: String) -> Void + + public init(_ closure: @escaping (_ level: String, _ tag: String, _ message: String) -> Void) { + self.closure = closure + } + + @inline(__always) + public func call(level: std.string, tag: std.string, message: std.string) -> Void { + self.closure(String(level), String(tag), String(message)) + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_void_std__string_std__string_std__string`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__string_std__string_std__string { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/nitrogen/generated/ios/swift/HybridRiveLoggerSpec.swift b/nitrogen/generated/ios/swift/HybridRiveLoggerSpec.swift new file mode 100644 index 00000000..0b2858e7 --- /dev/null +++ b/nitrogen/generated/ios/swift/HybridRiveLoggerSpec.swift @@ -0,0 +1,56 @@ +/// +/// HybridRiveLoggerSpec.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/// See ``HybridRiveLoggerSpec`` +public protocol HybridRiveLoggerSpec_protocol: HybridObject { + // Properties + + + // Methods + func setHandler(handler: @escaping (_ level: String, _ tag: String, _ message: String) -> Void) throws -> Void + func resetHandler() throws -> Void +} + +public extension HybridRiveLoggerSpec_protocol { + /// Default implementation of ``HybridObject.toString`` + func toString() -> String { + return "[HybridObject RiveLogger]" + } +} + +/// See ``HybridRiveLoggerSpec`` +open class HybridRiveLoggerSpec_base { + private weak var cxxWrapper: HybridRiveLoggerSpec_cxx? = nil + public init() { } + public func getCxxWrapper() -> HybridRiveLoggerSpec_cxx { + #if DEBUG + guard self is any HybridRiveLoggerSpec else { + fatalError("`self` is not a `HybridRiveLoggerSpec`! Did you accidentally inherit from `HybridRiveLoggerSpec_base` instead of `HybridRiveLoggerSpec`?") + } + #endif + if let cxxWrapper = self.cxxWrapper { + return cxxWrapper + } else { + let cxxWrapper = HybridRiveLoggerSpec_cxx(self as! any HybridRiveLoggerSpec) + self.cxxWrapper = cxxWrapper + return cxxWrapper + } + } +} + +/** + * A Swift base-protocol representing the RiveLogger HybridObject. + * Implement this protocol to create Swift-based instances of RiveLogger. + * ```swift + * class HybridRiveLogger : HybridRiveLoggerSpec { + * // ... + * } + * ``` + */ +public typealias HybridRiveLoggerSpec = HybridRiveLoggerSpec_protocol & HybridRiveLoggerSpec_base diff --git a/nitrogen/generated/ios/swift/HybridRiveLoggerSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridRiveLoggerSpec_cxx.swift new file mode 100644 index 00000000..5aaf184a --- /dev/null +++ b/nitrogen/generated/ios/swift/HybridRiveLoggerSpec_cxx.swift @@ -0,0 +1,153 @@ +/// +/// HybridRiveLoggerSpec_cxx.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * A class implementation that bridges HybridRiveLoggerSpec over to C++. + * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined. + * + * Also, some Swift types need to be bridged with special handling: + * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330) + * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper + * - Throwing methods need to be wrapped with a Result type, as exceptions cannot be propagated to C++ + */ +open class HybridRiveLoggerSpec_cxx { + /** + * The Swift <> C++ bridge's namespace (`margelo::nitro::rive::bridge::swift`) + * from `RNRive-Swift-Cxx-Bridge.hpp`. + * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift. + */ + public typealias bridge = margelo.nitro.rive.bridge.swift + + /** + * Holds an instance of the `HybridRiveLoggerSpec` Swift protocol. + */ + private var __implementation: any HybridRiveLoggerSpec + + /** + * Holds a weak pointer to the C++ class that wraps the Swift class. + */ + private var __cxxPart: bridge.std__weak_ptr_HybridRiveLoggerSpec_ + + /** + * Create a new `HybridRiveLoggerSpec_cxx` that wraps the given `HybridRiveLoggerSpec`. + * All properties and methods bridge to C++ types. + */ + public init(_ implementation: any HybridRiveLoggerSpec) { + self.__implementation = implementation + self.__cxxPart = .init() + /* no base class */ + } + + /** + * Get the actual `HybridRiveLoggerSpec` instance this class wraps. + */ + @inline(__always) + public func getHybridRiveLoggerSpec() -> any HybridRiveLoggerSpec { + return __implementation + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `HybridRiveLoggerSpec_cxx`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridRiveLoggerSpec_cxx { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } + + /** + * Gets (or creates) the C++ part of this Hybrid Object. + * The C++ part is a `std::shared_ptr`. + */ + public func getCxxPart() -> bridge.std__shared_ptr_HybridRiveLoggerSpec_ { + let cachedCxxPart = self.__cxxPart.lock() + if Bool(fromCxx: cachedCxxPart) { + return cachedCxxPart + } else { + let newCxxPart = bridge.create_std__shared_ptr_HybridRiveLoggerSpec_(self.toUnsafe()) + __cxxPart = bridge.weakify_std__shared_ptr_HybridRiveLoggerSpec_(newCxxPart) + return newCxxPart + } + } + + + + /** + * Get the memory size of the Swift class (plus size of any other allocations) + * so the JS VM can properly track it and garbage-collect the JS object if needed. + */ + @inline(__always) + public var memorySize: Int { + return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize + } + + /** + * Compares this object with the given [other] object for reference equality. + */ + @inline(__always) + public func equals(other: HybridRiveLoggerSpec_cxx) -> Bool { + return self.__implementation === other.__implementation + } + + /** + * Call dispose() on the Swift class. + * This _may_ be called manually from JS. + */ + @inline(__always) + public func dispose() { + self.__implementation.dispose() + } + + /** + * Call toString() on the Swift class. + */ + @inline(__always) + public func toString() -> String { + return self.__implementation.toString() + } + + // Properties + + + // Methods + @inline(__always) + public final func setHandler(handler: bridge.Func_void_std__string_std__string_std__string) -> bridge.Result_void_ { + do { + try self.__implementation.setHandler(handler: { () -> (String, String, String) -> Void in + let __wrappedFunction = bridge.wrap_Func_void_std__string_std__string_std__string(handler) + return { (__level: String, __tag: String, __message: String) -> Void in + __wrappedFunction.call(std.string(__level), std.string(__tag), std.string(__message)) + } + }()) + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } + + @inline(__always) + public final func resetHandler() -> bridge.Result_void_ { + do { + try self.__implementation.resetHandler() + return bridge.create_Result_void_() + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_void_(__exceptionPtr) + } + } +} diff --git a/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.cpp b/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.cpp new file mode 100644 index 00000000..5b46c37a --- /dev/null +++ b/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.cpp @@ -0,0 +1,22 @@ +/// +/// HybridRiveLoggerSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridRiveLoggerSpec.hpp" + +namespace margelo::nitro::rive { + + void HybridRiveLoggerSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridMethod("setHandler", &HybridRiveLoggerSpec::setHandler); + prototype.registerHybridMethod("resetHandler", &HybridRiveLoggerSpec::resetHandler); + }); + } + +} // namespace margelo::nitro::rive diff --git a/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.hpp b/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.hpp new file mode 100644 index 00000000..be55eb07 --- /dev/null +++ b/nitrogen/generated/shared/c++/HybridRiveLoggerSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridRiveLoggerSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + + + +#include +#include + +namespace margelo::nitro::rive { + + using namespace margelo::nitro; + + /** + * An abstract base class for `RiveLogger` + * Inherit this class to create instances of `HybridRiveLoggerSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridRiveLogger: public HybridRiveLoggerSpec { + * public: + * HybridRiveLogger(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridRiveLoggerSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridRiveLoggerSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridRiveLoggerSpec() override = default; + + public: + // Properties + + + public: + // Methods + virtual void setHandler(const std::function& handler) = 0; + virtual void resetHandler() = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "RiveLogger"; + }; + +} // namespace margelo::nitro::rive diff --git a/src/core/RiveLogger.ts b/src/core/RiveLogger.ts new file mode 100644 index 00000000..afb80149 --- /dev/null +++ b/src/core/RiveLogger.ts @@ -0,0 +1,29 @@ +import { NitroModules } from 'react-native-nitro-modules'; +import type { RiveLogger as RiveLoggerSpec } from '../specs/RiveLogger.nitro'; + +const _logger = NitroModules.createHybridObject('RiveLogger'); + +function defaultHandler(level: string, tag: string, message: string) { + const prefix = `[Rive/${tag}]`; + if (level === 'error') { + console.error(prefix, message); + } else if (level === 'warn') { + console.warn(prefix, message); + } else { + console.log(prefix, message); + } +} + +_logger.setHandler(defaultHandler); + +export namespace RiveLog { + export function setHandler( + handler: (level: string, tag: string, message: string) => void + ) { + _logger.setHandler(handler); + } + + export function resetHandler() { + _logger.setHandler(defaultHandler); + } +} diff --git a/src/index.tsx b/src/index.tsx index fdbf4423..510b176f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -66,4 +66,5 @@ export { useRiveFile, type UseRiveFileResult } from './hooks/useRiveFile'; export { type RiveFileInput } from './hooks/useRiveFile'; export { type SetValueAction } from './types'; export { RiveRuntime } from './core/RiveRuntime'; +export { RiveLog } from './core/RiveLogger'; export { DataBindMode }; diff --git a/src/specs/RiveLogger.nitro.ts b/src/specs/RiveLogger.nitro.ts new file mode 100644 index 00000000..54594ea4 --- /dev/null +++ b/src/specs/RiveLogger.nitro.ts @@ -0,0 +1,9 @@ +import type { HybridObject } from 'react-native-nitro-modules'; + +export interface RiveLogger + extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { + setHandler( + handler: (level: string, tag: string, message: string) => void + ): void; + resetHandler(): void; +}