From 766b2a455cc92a06bfa9a2df5553bc5c41866a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 18:02:41 +0200 Subject: [PATCH 1/9] feat: add RiveLogger with configurable JS handler and deprecation warnings Adds a general-purpose logging system (RiveLogger) that surfaces native logs to JS. By default logs show in the RN console via console.error/warn/log. Users can set a custom handler to filter, suppress, or forward logs (e.g. to Sentry). On Android, Rive C++ runtime logs are also unified through RiveLog. Deprecated blocking methods now emit once-per-session warnings via the DeprecationWarning utility. --- .../margelo/nitro/rive/DeprecationWarning.kt | 12 ++ .../com/margelo/nitro/rive/HybridRiveFile.kt | 18 ++- .../nitro/rive/HybridRiveFileFactory.kt | 17 +- .../margelo/nitro/rive/HybridRiveLogger.kt | 16 ++ .../com/margelo/nitro/rive/HybridViewModel.kt | 18 ++- .../rive/HybridViewModelBooleanProperty.kt | 3 +- .../rive/HybridViewModelColorProperty.kt | 3 +- .../nitro/rive/HybridViewModelEnumProperty.kt | 3 +- .../nitro/rive/HybridViewModelInstance.kt | 3 +- .../nitro/rive/HybridViewModelListProperty.kt | 6 +- .../rive/HybridViewModelNumberProperty.kt | 3 +- .../rive/HybridViewModelStringProperty.kt | 3 +- .../java/com/margelo/nitro/rive/RiveLog.kt | 23 +++ ios/new/DeprecationWarning.swift | 10 ++ ios/new/HybridRiveFile.swift | 12 +- ios/new/HybridRiveLogger.swift | 11 ++ ios/new/HybridViewModel.swift | 10 +- ios/new/HybridViewModelBooleanProperty.swift | 3 +- ios/new/HybridViewModelColorProperty.swift | 3 +- ios/new/HybridViewModelEnumProperty.swift | 3 +- ios/new/HybridViewModelInstance.swift | 3 +- ios/new/HybridViewModelListProperty.swift | 4 +- ios/new/HybridViewModelNumberProperty.swift | 3 +- ios/new/HybridViewModelStringProperty.swift | 3 +- ios/new/RiveLog.swift | 35 ++++ nitro.json | 4 + ...id_std__string_std__string_std__string.hpp | 76 +++++++++ .../android/c++/JHybridRiveLoggerSpec.cpp | 59 +++++++ .../android/c++/JHybridRiveLoggerSpec.hpp | 64 ++++++++ ...oid_std__string_std__string_std__string.kt | 80 +++++++++ .../nitro/rive/HybridRiveLoggerSpec.kt | 63 ++++++++ .../generated/android/rive+autolinking.cmake | 2 + nitrogen/generated/android/riveOnLoad.cpp | 18 +++ .../generated/ios/RNRive-Swift-Cxx-Bridge.cpp | 25 +++ .../generated/ios/RNRive-Swift-Cxx-Bridge.hpp | 39 +++++ .../ios/RNRive-Swift-Cxx-Umbrella.hpp | 5 + nitrogen/generated/ios/RNRiveAutolinking.mm | 8 + .../generated/ios/RNRiveAutolinking.swift | 12 ++ .../ios/c++/HybridRiveLoggerSpecSwift.cpp | 11 ++ .../ios/c++/HybridRiveLoggerSpecSwift.hpp | 87 ++++++++++ ..._std__string_std__string_std__string.swift | 46 ++++++ .../ios/swift/HybridRiveLoggerSpec.swift | 56 +++++++ .../ios/swift/HybridRiveLoggerSpec_cxx.swift | 153 ++++++++++++++++++ .../shared/c++/HybridRiveLoggerSpec.cpp | 22 +++ .../shared/c++/HybridRiveLoggerSpec.hpp | 64 ++++++++ src/core/RiveLogger.ts | 29 ++++ src/index.tsx | 1 + src/specs/RiveLogger.nitro.ts | 9 ++ 48 files changed, 1120 insertions(+), 41 deletions(-) create mode 100644 android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt create mode 100644 android/src/new/java/com/margelo/nitro/rive/HybridRiveLogger.kt create mode 100644 android/src/new/java/com/margelo/nitro/rive/RiveLog.kt create mode 100644 ios/new/DeprecationWarning.swift create mode 100644 ios/new/HybridRiveLogger.swift create mode 100644 ios/new/RiveLog.swift create mode 100644 nitrogen/generated/android/c++/JFunc_void_std__string_std__string_std__string.hpp create mode 100644 nitrogen/generated/android/c++/JHybridRiveLoggerSpec.cpp create mode 100644 nitrogen/generated/android/c++/JHybridRiveLoggerSpec.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/rive/Func_void_std__string_std__string_std__string.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridRiveLoggerSpec.kt create mode 100644 nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.cpp create mode 100644 nitrogen/generated/ios/c++/HybridRiveLoggerSpecSwift.hpp create mode 100644 nitrogen/generated/ios/swift/Func_void_std__string_std__string_std__string.swift create mode 100644 nitrogen/generated/ios/swift/HybridRiveLoggerSpec.swift create mode 100644 nitrogen/generated/ios/swift/HybridRiveLoggerSpec_cxx.swift create mode 100644 nitrogen/generated/shared/c++/HybridRiveLoggerSpec.cpp create mode 100644 nitrogen/generated/shared/c++/HybridRiveLoggerSpec.hpp create mode 100644 src/core/RiveLogger.ts create mode 100644 src/specs/RiveLogger.nitro.ts diff --git a/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt b/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt new file mode 100644 index 00000000..de054b4a --- /dev/null +++ b/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt @@ -0,0 +1,12 @@ +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/new/java/com/margelo/nitro/rive/HybridRiveFile.kt b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt index 4b30374b..c56c35af 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt @@ -26,11 +26,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 +45,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 +53,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 +69,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 +108,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 +124,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 +144,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..30c960a6 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,13 @@ 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/HybridRiveLogger.kt b/android/src/new/java/com/margelo/nitro/rive/HybridRiveLogger.kt new file mode 100644 index 00000000..552296e8 --- /dev/null +++ b/android/src/new/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/new/java/com/margelo/nitro/rive/HybridViewModel.kt b/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt index b73f6772..6e92c58e 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt @@ -33,22 +33,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 +70,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 +81,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 +97,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 +116,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 +137,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..080afc53 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt @@ -22,10 +22,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..1eb3a6c7 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt @@ -22,10 +22,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..35ebbd5e 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt @@ -22,10 +22,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..3434a3ee 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt @@ -22,10 +22,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..44e9bc96 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt @@ -22,10 +22,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/android/src/new/java/com/margelo/nitro/rive/RiveLog.kt b/android/src/new/java/com/margelo/nitro/rive/RiveLog.kt new file mode 100644 index 00000000..c6c2ae73 --- /dev/null +++ b/android/src/new/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/ios/new/DeprecationWarning.swift b/ios/new/DeprecationWarning.swift new file mode 100644 index 00000000..1b8d48f1 --- /dev/null +++ b/ios/new/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/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/HybridRiveLogger.swift b/ios/new/HybridRiveLogger.swift new file mode 100644 index 00000000..7ae96295 --- /dev/null +++ b/ios/new/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/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/RiveLog.swift b/ios/new/RiveLog.swift new file mode 100644 index 00000000..2f724c75 --- /dev/null +++ b/ios/new/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/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..aa4bc478 --- /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 RiveLogger { + 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..4c11ebd8 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 { RiveLogger } 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; +} From 4fc6d994a06c0fceee78d682faadc0b93d193400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 18:14:55 +0200 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20resolve=20ktlint=20errors=20?= =?UTF-8?q?=E2=80=94=20unused=20imports=20and=20statement=20wrapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/margelo/nitro/rive/DeprecationWarning.kt | 6 ++++-- .../com/margelo/nitro/rive/HybridRiveFile.kt | 1 - .../margelo/nitro/rive/HybridRiveFileFactory.kt | 16 ++++++++++++---- .../com/margelo/nitro/rive/HybridViewModel.kt | 1 - .../nitro/rive/HybridViewModelBooleanProperty.kt | 1 - .../nitro/rive/HybridViewModelColorProperty.kt | 1 - .../nitro/rive/HybridViewModelEnumProperty.kt | 1 - .../nitro/rive/HybridViewModelNumberProperty.kt | 1 - .../nitro/rive/HybridViewModelStringProperty.kt | 1 - 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt b/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt index de054b4a..90efb5b7 100644 --- a/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt +++ b/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt @@ -5,8 +5,10 @@ object DeprecationWarning { fun warn(method: String, replacement: String) { if (warned.add(method)) { - RiveLog.w("Deprecation", - "'$method' is deprecated and blocks the calling thread. Use '$replacement' instead.") + RiveLog.w( + "Deprecation", + "'$method' is deprecated and blocks the calling thread. Use '$replacement' instead.", + ) } } } 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 c56c35af..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 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 30c960a6..22ce6447 100644 --- a/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt +++ b/android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt @@ -46,10 +46,18 @@ object RiveErrorLogger : app.rive.RiveLog.Logger { synchronized(reportedErrors) { reportedErrors.clear() } } - 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 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() RiveLog.e(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 6e92c58e..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 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 080afc53..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 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 1eb3a6c7..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 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 35ebbd5e..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 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 3434a3ee..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 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 44e9bc96..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 From b73f5cd8227f65ddd835c041b4810d46e80ca33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 18:32:19 +0200 Subject: [PATCH 3/9] feat: rename public TS API to RiveLog, hook into iOS SDK's pluggable logger Renames the exported namespace from RiveLogger to RiveLog to match the native SDK naming convention. Implements RiveRuntime.RiveLog.Logger on iOS to unify C++ runtime logs through our bridge logging system (parity with Android's RiveErrorLogger). --- ios/new/HybridRiveFileFactory.swift | 7 +++++- ios/new/RiveRuntimeLogger.swift | 39 +++++++++++++++++++++++++++++ src/core/RiveLogger.ts | 2 +- src/index.tsx | 2 +- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 ios/new/RiveRuntimeLogger.swift 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/RiveRuntimeLogger.swift b/ios/new/RiveRuntimeLogger.swift new file mode 100644 index 00000000..36ea5648 --- /dev/null +++ b/ios/new/RiveRuntimeLogger.swift @@ -0,0 +1,39 @@ +@_spi(RiveExperimental) import RiveRuntime + +/// 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(tag.category, message()) + } + + func debug(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.d(tag.category, message()) + } + + func trace(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.d(tag.category, message()) + } + + func info(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.i(tag.category, message()) + } + + func error(tag: RiveRuntime.RiveLog.Tag, error: (any Error)?, _ message: @escaping () -> String) { + let suffix = error.map { " (\($0.localizedDescription))" } ?? "" + RiveLog.e(tag.category, "\(message())\(suffix)") + } + + func warning(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.w(tag.category, message()) + } + + func fault(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.e(tag.category, message()) + } + + func critical(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { + RiveLog.e(tag.category, message()) + } +} diff --git a/src/core/RiveLogger.ts b/src/core/RiveLogger.ts index aa4bc478..afb80149 100644 --- a/src/core/RiveLogger.ts +++ b/src/core/RiveLogger.ts @@ -16,7 +16,7 @@ function defaultHandler(level: string, tag: string, message: string) { _logger.setHandler(defaultHandler); -export namespace RiveLogger { +export namespace RiveLog { export function setHandler( handler: (level: string, tag: string, message: string) => void ) { diff --git a/src/index.tsx b/src/index.tsx index 4c11ebd8..510b176f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -66,5 +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 { RiveLogger } from './core/RiveLogger'; +export { RiveLog } from './core/RiveLogger'; export { DataBindMode }; From 974057e800170cb385608d60ab46cd8d65e59ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 21:48:01 +0200 Subject: [PATCH 4/9] refactor: move RiveLog, DeprecationWarning, HybridRiveLogger to shared locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These utilities must be accessible to both legacy and new backends. iOS: ios/new/ → ios/ (shared root). Android: src/new/ → src/main/ (shared). RiveRuntimeLogger stays in ios/new/ since it depends on the experimental SDK. --- .../java/com/margelo/nitro/rive/DeprecationWarning.kt | 0 .../{new => main}/java/com/margelo/nitro/rive/HybridRiveLogger.kt | 0 android/src/{new => main}/java/com/margelo/nitro/rive/RiveLog.kt | 0 ios/{new => }/DeprecationWarning.swift | 0 ios/{new => }/HybridRiveLogger.swift | 0 ios/{new => }/RiveLog.swift | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename android/src/{new => main}/java/com/margelo/nitro/rive/DeprecationWarning.kt (100%) rename android/src/{new => main}/java/com/margelo/nitro/rive/HybridRiveLogger.kt (100%) rename android/src/{new => main}/java/com/margelo/nitro/rive/RiveLog.kt (100%) rename ios/{new => }/DeprecationWarning.swift (100%) rename ios/{new => }/HybridRiveLogger.swift (100%) rename ios/{new => }/RiveLog.swift (100%) diff --git a/android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt b/android/src/main/java/com/margelo/nitro/rive/DeprecationWarning.kt similarity index 100% rename from android/src/new/java/com/margelo/nitro/rive/DeprecationWarning.kt rename to android/src/main/java/com/margelo/nitro/rive/DeprecationWarning.kt diff --git a/android/src/new/java/com/margelo/nitro/rive/HybridRiveLogger.kt b/android/src/main/java/com/margelo/nitro/rive/HybridRiveLogger.kt similarity index 100% rename from android/src/new/java/com/margelo/nitro/rive/HybridRiveLogger.kt rename to android/src/main/java/com/margelo/nitro/rive/HybridRiveLogger.kt diff --git a/android/src/new/java/com/margelo/nitro/rive/RiveLog.kt b/android/src/main/java/com/margelo/nitro/rive/RiveLog.kt similarity index 100% rename from android/src/new/java/com/margelo/nitro/rive/RiveLog.kt rename to android/src/main/java/com/margelo/nitro/rive/RiveLog.kt diff --git a/ios/new/DeprecationWarning.swift b/ios/DeprecationWarning.swift similarity index 100% rename from ios/new/DeprecationWarning.swift rename to ios/DeprecationWarning.swift diff --git a/ios/new/HybridRiveLogger.swift b/ios/HybridRiveLogger.swift similarity index 100% rename from ios/new/HybridRiveLogger.swift rename to ios/HybridRiveLogger.swift diff --git a/ios/new/RiveLog.swift b/ios/RiveLog.swift similarity index 100% rename from ios/new/RiveLog.swift rename to ios/RiveLog.swift From df24c280ef8ccf17a7e43ea576d3503a08720e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 31 Mar 2026 21:55:44 +0200 Subject: [PATCH 5/9] test: add harness tests for RiveLog handler and deprecation warnings --- example/__tests__/rivelog.harness.tsx | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 example/__tests__/rivelog.harness.tsx diff --git a/example/__tests__/rivelog.harness.tsx b/example/__tests__/rivelog.harness.tsx new file mode 100644 index 00000000..5e90830c --- /dev/null +++ b/example/__tests__/rivelog.harness.tsx @@ -0,0 +1,78 @@ +import { describe, it, expect, cleanup } from 'react-native-harness'; +import { RiveFileFactory, RiveLog } from '@rive-app/react-native'; + +const BOUNCING_BALL = require('../assets/rive/bouncing_ball.riv'); + +type LogEntry = { level: string; tag: string; message: string }; + +describe('RiveLog', () => { + it('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(); + + 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'); + + RiveLog.resetHandler(); + cleanup(); + }); + + it('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; + + const deprecations = logs.filter( + (l) => l.tag === 'Deprecation' && l.message.includes('artboardNames') + ); + expect(deprecations.length).toBe(1); + + 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'); + + // If we got here without throwing, the default handler works. + expect(true).toBe(true); + + cleanup(); + }); +}); From 667e5f72745d597bc85d00755977f37454bf167f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 1 Apr 2026 07:50:47 +0200 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20resolve=20CI=20failures=20=E2=80=94?= =?UTF-8?q?=20Tag.category=20not=20in=20SDK,=20async=20test=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RiveLog.Tag doesn't have a .category property in the installed SDK version (6.18.0) — use a local switch to map tag cases to strings. Also wrap deprecation assertions in waitFor to handle async Nitro callback delivery on Android. --- example/__tests__/rivelog.harness.tsx | 29 +++++++++++++++-------- ios/new/RiveRuntimeLogger.swift | 33 ++++++++++++++++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/example/__tests__/rivelog.harness.tsx b/example/__tests__/rivelog.harness.tsx index 5e90830c..fe803b46 100644 --- a/example/__tests__/rivelog.harness.tsx +++ b/example/__tests__/rivelog.harness.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect, cleanup } from 'react-native-harness'; +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'); @@ -16,11 +16,16 @@ describe('RiveLog', () => { file.defaultArtboardViewModel(); - 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'); + 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(); @@ -39,10 +44,15 @@ describe('RiveLog', () => { file.artboardNames; file.artboardNames; - const deprecations = logs.filter( - (l) => l.tag === 'Deprecation' && l.message.includes('artboardNames') + await waitFor( + () => { + const deprecations = logs.filter( + (l) => l.tag === 'Deprecation' && l.message.includes('artboardNames') + ); + expect(deprecations.length).toBe(1); + }, + { timeout: 2000 } ); - expect(deprecations.length).toBe(1); RiveLog.resetHandler(); cleanup(); @@ -70,7 +80,6 @@ describe('RiveLog', () => { file.viewModelByName('nonexistent'); - // If we got here without throwing, the default handler works. expect(true).toBe(true); cleanup(); diff --git a/ios/new/RiveRuntimeLogger.swift b/ios/new/RiveRuntimeLogger.swift index 36ea5648..3ed6aea7 100644 --- a/ios/new/RiveRuntimeLogger.swift +++ b/ios/new/RiveRuntimeLogger.swift @@ -1,39 +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(tag.category, message()) + RiveLog.i(tagName(tag), message()) } func debug(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.d(tag.category, message()) + RiveLog.d(tagName(tag), message()) } func trace(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.d(tag.category, message()) + RiveLog.d(tagName(tag), message()) } func info(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.i(tag.category, message()) + 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(tag.category, "\(message())\(suffix)") + RiveLog.e(tagName(tag), "\(message())\(suffix)") } func warning(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.w(tag.category, message()) + RiveLog.w(tagName(tag), message()) } func fault(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.e(tag.category, message()) + RiveLog.e(tagName(tag), message()) } func critical(tag: RiveRuntime.RiveLog.Tag, _ message: @escaping () -> String) { - RiveLog.e(tag.category, message()) + RiveLog.e(tagName(tag), message()) } } From 0bbdaf9f19643eed014fbdd65bb63cf758aa5f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 1 Apr 2026 13:43:53 +0200 Subject: [PATCH 7/9] fix: skip deprecation tests on legacy backend Legacy backend doesn't have DeprecationWarning.warn() calls in its deprecated methods, so deprecation-specific tests are skipped when running with the legacy runtime. --- example/__tests__/rivelog.harness.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example/__tests__/rivelog.harness.tsx b/example/__tests__/rivelog.harness.tsx index fe803b46..38a61eec 100644 --- a/example/__tests__/rivelog.harness.tsx +++ b/example/__tests__/rivelog.harness.tsx @@ -2,11 +2,13 @@ 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', () => { - it('captures deprecation warning from sync method', async () => { + // 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 }); @@ -31,7 +33,7 @@ describe('RiveLog', () => { cleanup(); }); - it('emits each deprecation only once', async () => { + (isExperimental ? it : it.skip)('emits each deprecation only once', async () => { const logs: LogEntry[] = []; RiveLog.setHandler((level, tag, message) => { logs.push({ level, tag, message }); From 508a6712ed79707d33f616734e0022fcecb46145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 1 Apr 2026 14:20:16 +0200 Subject: [PATCH 8/9] fix: prettier formatting --- example/__tests__/rivelog.harness.tsx | 111 ++++++++++++++------------ 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/example/__tests__/rivelog.harness.tsx b/example/__tests__/rivelog.harness.tsx index 38a61eec..3da82c1a 100644 --- a/example/__tests__/rivelog.harness.tsx +++ b/example/__tests__/rivelog.harness.tsx @@ -8,57 +8,66 @@ 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(); - }); + (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[] = []; From c4129e5ba4605993b51350ca8832a1d08863abb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 8 Apr 2026 20:45:19 +0200 Subject: [PATCH 9/9] fix(android): use RiveLog.w instead of Log.w in inferFromMagicBytes --- .../java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 } }