react-native-worklets 0.8.0 → 0.9.0-nightly-20260320-548038843

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/Common/cpp/worklets/SharedItems/Serializable.h +2 -2
  2. package/Common/cpp/worklets/Tools/WorkletsJSIUtils.h +3 -3
  3. package/Common/cpp/worklets/WorkletRuntime/UIRuntimeDecorator.cpp +3 -2
  4. package/Common/cpp/worklets/WorkletRuntime/UIRuntimeDecorator.h +2 -1
  5. package/android/src/main/java/com/swmansion/worklets/AndroidUIScheduler.kt +54 -0
  6. package/android/src/main/java/com/swmansion/worklets/JSCallInvokerResolver.kt +28 -0
  7. package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.kt +89 -0
  8. package/android/src/main/java/com/swmansion/worklets/WorkletsMessageQueueThread.kt +11 -0
  9. package/android/src/main/java/com/swmansion/worklets/WorkletsMessageQueueThreadBase.kt +59 -0
  10. package/android/src/main/java/com/swmansion/worklets/WorkletsPackage.kt +39 -0
  11. package/android/src/main/java/com/swmansion/worklets/runloop/AnimationFrameCallback.kt +11 -0
  12. package/android/src/main/java/com/swmansion/worklets/runloop/AnimationFrameQueue.kt +106 -0
  13. package/android/src/networking/com/swmansion/worklets/WorkletsModule.kt +164 -0
  14. package/android/src/no-networking/com/swmansion/worklets/WorkletsModule.kt +127 -0
  15. package/lib/module/debug/jsVersion.js +1 -1
  16. package/lib/module/debug/jsVersion.js.map +1 -1
  17. package/lib/module/memory/serializable.native.js +3 -4
  18. package/lib/module/memory/serializable.native.js.map +1 -1
  19. package/lib/typescript/debug/jsVersion.d.ts +1 -1
  20. package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
  21. package/lib/typescript/memory/serializable.native.d.ts.map +1 -1
  22. package/package.json +9 -9
  23. package/src/debug/jsVersion.ts +1 -1
  24. package/src/memory/serializable.native.ts +3 -4
  25. package/android/src/main/java/com/swmansion/worklets/AndroidUIScheduler.java +0 -60
  26. package/android/src/main/java/com/swmansion/worklets/JSCallInvokerResolver.java +0 -27
  27. package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.java +0 -88
  28. package/android/src/main/java/com/swmansion/worklets/WorkletsMessageQueueThread.java +0 -16
  29. package/android/src/main/java/com/swmansion/worklets/WorkletsMessageQueueThreadBase.java +0 -73
  30. package/android/src/main/java/com/swmansion/worklets/WorkletsPackage.java +0 -48
  31. package/android/src/main/java/com/swmansion/worklets/runloop/AnimationFrameCallback.java +0 -19
  32. package/android/src/main/java/com/swmansion/worklets/runloop/AnimationFrameQueue.java +0 -113
  33. package/android/src/networking/com/swmansion/worklets/WorkletsModule.java +0 -174
  34. package/android/src/no-networking/com/swmansion/worklets/WorkletsModule.java +0 -137
@@ -52,7 +52,7 @@ class RetainingSerializable : virtual public BaseClass {
52
52
  explicit RetainingSerializable(jsi::Runtime &rt, Args &&...args)
53
53
  : BaseClass(rt, std::forward<Args>(args)...), primaryRuntime_(&rt) {}
54
54
 
55
- jsi::Value toJSValue(jsi::Runtime &rt) {
55
+ jsi::Value toJSValue(jsi::Runtime &rt) override {
56
56
  if (&rt == primaryRuntime_) {
57
57
  // TODO: it is suboptimal to generate new object every time getJS is
58
58
  // called on host runtime – the objects we are generating already exists
@@ -74,7 +74,7 @@ class RetainingSerializable : virtual public BaseClass {
74
74
  return BaseClass::toJSValue(rt);
75
75
  }
76
76
 
77
- ~RetainingSerializable() {
77
+ ~RetainingSerializable() override {
78
78
  cleanupIfRuntimeExists(secondaryRuntime_, secondaryValue_);
79
79
  }
80
80
  };
@@ -84,7 +84,7 @@ getArgsForFunction(std::function<Ret(Args...)>, jsi::Runtime &rt, const jsi::Val
84
84
  // passing `rt` as the first argument
85
85
  template <typename Ret, typename... Args>
86
86
  std::tuple<jsi::Runtime &, Args...> getArgsForFunction(
87
- std::function<Ret(jsi::Runtime &, Args...)>,
87
+ const std::function<Ret(jsi::Runtime &, Args...)> &,
88
88
  jsi::Runtime &rt,
89
89
  const jsi::Value *args,
90
90
  const size_t count) {
@@ -116,7 +116,7 @@ inline jsi::Value apply(std::function<void(Args...)> function, std::tuple<Args..
116
116
  // returns a function with JSI calling convention
117
117
  // from a native function `function`
118
118
  template <typename Fun>
119
- jsi::HostFunctionType createHostFunction(Fun function) {
119
+ jsi::HostFunctionType createHostFunction(const Fun &function) {
120
120
  return [function](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, const size_t count) {
121
121
  auto argz = getArgsForFunction(function, rt, args, count);
122
122
  return apply(function, std::move(argz));
@@ -150,7 +150,7 @@ struct takes_runtime<jsi::Runtime &, Rest...> {
150
150
  // and installs it as a global function named `name`
151
151
  // in the `rt` JS runtime
152
152
  template <typename Ret, typename... Args>
153
- void installJsiFunction(jsi::Runtime &rt, std::string_view name, std::function<Ret(Args...)> function) {
153
+ void installJsiFunction(jsi::Runtime &rt, std::string_view name, const std::function<Ret(Args...)> &function) {
154
154
  auto clb = createHostFunction(function);
155
155
  auto argsCount = sizeof...(Args) - takes_runtime<Args...>::value;
156
156
  jsi::Value jsiFunction =
@@ -8,12 +8,13 @@ namespace worklets {
8
8
 
9
9
  void UIRuntimeDecorator::decorate(
10
10
  facebook::jsi::Runtime &uiRuntime,
11
- std::function<void(facebook::jsi::Runtime &rt, const facebook::jsi::Value &callback)> &&requestAnimationFrame) {
11
+ const std::function<void(facebook::jsi::Runtime &rt, const facebook::jsi::Value &callback)>
12
+ &requestAnimationFrame) {
12
13
  uiRuntime.global().setProperty(uiRuntime, runtimeKindBindingName, static_cast<int>(RuntimeKind::UI));
13
14
 
14
15
  uiRuntime.global().setProperty(uiRuntime, "_UI", true);
15
16
 
16
- jsi_utils::installJsiFunction(uiRuntime, "requestAnimationFrame", std::move(requestAnimationFrame));
17
+ jsi_utils::installJsiFunction(uiRuntime, "requestAnimationFrame", requestAnimationFrame);
17
18
  }
18
19
 
19
20
  } // namespace worklets
@@ -8,7 +8,8 @@ class UIRuntimeDecorator {
8
8
  public:
9
9
  static void decorate(
10
10
  facebook::jsi::Runtime &uiRuntime,
11
- std::function<void(facebook::jsi::Runtime &rt, const facebook::jsi::Value &callback)> &&requestAnimationFrame);
11
+ const std::function<void(facebook::jsi::Runtime &rt, const facebook::jsi::Value &callback)>
12
+ &requestAnimationFrame);
12
13
  };
13
14
 
14
15
  } // namespace worklets
@@ -0,0 +1,54 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.jni.HybridData
4
+ import com.facebook.proguard.annotations.DoNotStrip
5
+ import com.facebook.react.bridge.GuardedRunnable
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.UiThreadUtil
8
+ import java.util.concurrent.atomic.AtomicBoolean
9
+
10
+ @Suppress("KotlinJniMissingFunction")
11
+ class AndroidUIScheduler(context: ReactApplicationContext) {
12
+
13
+ @field:DoNotStrip
14
+ private val mHybridData: HybridData = initHybrid()
15
+
16
+ private val mContext: ReactApplicationContext = context
17
+ private val mActive = AtomicBoolean(true)
18
+
19
+ private val mUIThreadRunnable = Runnable {
20
+ // This callback is called on the UI thread, but the module is invalidated on the JS
21
+ // thread. Therefore, we must synchronize for reloads. Without synchronization the cpp part
22
+ // gets torn down while the UI thread is still executing it, leading to crashes.
23
+ synchronized(mActive) {
24
+ if (mActive.get()) {
25
+ triggerUI()
26
+ }
27
+ }
28
+ }
29
+
30
+ private external fun initHybrid(): HybridData
31
+
32
+ external fun triggerUI()
33
+
34
+ external fun invalidate()
35
+
36
+ @DoNotStrip
37
+ @Suppress("unused")
38
+ private fun scheduleTriggerOnUI() {
39
+ UiThreadUtil.runOnUiThread(
40
+ object : GuardedRunnable(mContext.exceptionHandler) {
41
+ override fun runGuarded() {
42
+ mUIThreadRunnable.run()
43
+ }
44
+ }
45
+ )
46
+ }
47
+
48
+ fun deactivate() {
49
+ synchronized(mActive) {
50
+ mActive.set(false)
51
+ invalidate()
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,28 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.common.annotations.FrameworkAPI
5
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
6
+
7
+ object JSCallInvokerResolver {
8
+
9
+ @OptIn(FrameworkAPI::class)
10
+ @JvmStatic
11
+ fun getJSCallInvokerHolder(context: ReactApplicationContext): CallInvokerHolderImpl {
12
+ try {
13
+ val method = context.javaClass.getMethod("getJSCallInvokerHolder")
14
+ return method.invoke(context) as CallInvokerHolderImpl
15
+ } catch (_: Exception) {
16
+ // In newer implementations, the method is in CatalystInstance, continue.
17
+ }
18
+ try {
19
+ val catalystInstance =
20
+ context.javaClass.getMethod("getCatalystInstance").invoke(context)
21
+ checkNotNull(catalystInstance)
22
+ val method = catalystInstance.javaClass.getMethod("getJSCallInvokerHolder")
23
+ return method.invoke(catalystInstance) as CallInvokerHolderImpl
24
+ } catch (e: Exception) {
25
+ throw RuntimeException("Failed to get JSCallInvokerHolder", e)
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,89 @@
1
+ package com.swmansion.worklets
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.res.AssetManager
5
+ import android.os.Build
6
+ import com.facebook.jni.HybridData
7
+ import com.facebook.proguard.annotations.DoNotStrip
8
+ import com.facebook.proguard.annotations.DoNotStripAny
9
+ import java.io.IOException
10
+ import java.io.InputStream
11
+ import java.net.HttpURLConnection
12
+ import java.net.URL
13
+ import java.nio.charset.StandardCharsets
14
+
15
+ /** A wrapper around a JavaScript bundle that is backed by a native C++ object. */
16
+ @Suppress("KotlinJniMissingFunction")
17
+ @SuppressLint("MissingNativeLoadLibrary")
18
+ @DoNotStripAny
19
+ class ScriptBufferWrapper(uri: String, assetManager: AssetManager) {
20
+
21
+ @field:DoNotStrip
22
+ private val mHybridData: HybridData
23
+
24
+ init {
25
+ val filePrefix = "file://"
26
+ val assetsPrefix = "assets://"
27
+
28
+ mHybridData = when {
29
+ uri.startsWith(filePrefix) -> {
30
+ val fileName = uri.substring(filePrefix.length)
31
+ initHybridFromFile(fileName)
32
+ }
33
+ uri.startsWith(assetsPrefix) -> {
34
+ val assetURL = uri.substring(assetsPrefix.length)
35
+ initHybridFromAssets(assetManager, assetURL)
36
+ }
37
+ else -> {
38
+ val scriptContent = try {
39
+ downloadScript(uri)
40
+ } catch (e: IOException) {
41
+ throw RuntimeException(e)
42
+ }
43
+ initHybridFromString(scriptContent, uri)
44
+ }
45
+ }
46
+ }
47
+
48
+ private external fun initHybridFromAssets(assetManager: AssetManager, assetURL: String): HybridData
49
+
50
+ private external fun initHybridFromFile(fileName: String): HybridData
51
+
52
+ private external fun initHybridFromString(script: String, url: String): HybridData
53
+
54
+ companion object {
55
+ private fun downloadScript(url: String): String {
56
+ val scriptUrl = URL(url)
57
+ val connection = scriptUrl.openConnection() as HttpURLConnection
58
+ try {
59
+ val content: ByteArray
60
+
61
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
62
+ content = connection.inputStream.readAllBytes()
63
+ } else {
64
+ content = readBytes(connection.inputStream)
65
+ }
66
+
67
+ return String(content, StandardCharsets.UTF_8)
68
+ } finally {
69
+ connection.disconnect()
70
+ }
71
+ }
72
+
73
+ /** Reads all bytes from an InputStream into a byte array for SDKs below 33. */
74
+ private fun readBytes(inputStream: InputStream): ByteArray {
75
+ val bufferSize = 4096
76
+ try {
77
+ val byteBuffer = java.io.ByteArrayOutputStream()
78
+ val buffer = ByteArray(bufferSize)
79
+ var len: Int
80
+ while (inputStream.read(buffer).also { len = it } != -1) {
81
+ byteBuffer.write(buffer, 0, len)
82
+ }
83
+ return byteBuffer.toByteArray()
84
+ } finally {
85
+ inputStream.close()
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,11 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+
5
+ @DoNotStrip
6
+ class WorkletsMessageQueueThread : WorkletsMessageQueueThreadBase() {
7
+
8
+ override fun runOnQueue(runnable: Runnable): Boolean = messageQueueThread.runOnQueue(runnable)
9
+
10
+ override fun isIdle(): Boolean = messageQueueThread.isIdle()
11
+ }
@@ -0,0 +1,59 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import com.facebook.react.bridge.queue.MessageQueueThread
5
+ import com.facebook.react.bridge.queue.MessageQueueThreadImpl
6
+ import com.facebook.react.bridge.queue.MessageQueueThreadPerfStats
7
+ import com.facebook.react.bridge.queue.MessageQueueThreadSpec
8
+ import java.util.concurrent.Callable
9
+ import java.util.concurrent.Future
10
+
11
+ // This class is an almost exact copy of MessageQueueThreadImpl taken from here:
12
+ // https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.kt
13
+ // The only method that has changed is `quitSynchronous()` (see comment above
14
+ // function implementation for details).
15
+ @DoNotStrip
16
+ abstract class WorkletsMessageQueueThreadBase : MessageQueueThread {
17
+
18
+ protected val messageQueueThread: MessageQueueThreadImpl =
19
+ MessageQueueThreadImpl.create(
20
+ MessageQueueThreadSpec.mainThreadSpec()
21
+ ) { exception ->
22
+ throw RuntimeException(exception)
23
+ }
24
+
25
+ override fun <T> callOnQueue(callable: Callable<T>): Future<T> =
26
+ messageQueueThread.callOnQueue(callable)
27
+
28
+ override fun isOnThread(): Boolean = messageQueueThread.isOnThread()
29
+
30
+ override fun assertIsOnThread() {
31
+ messageQueueThread.assertIsOnThread()
32
+ }
33
+
34
+ override fun assertIsOnThread(s: String) {
35
+ messageQueueThread.assertIsOnThread(s)
36
+ }
37
+
38
+ // We don't want to quit the main looper (which is what MessageQueueThreadImpl would have done),
39
+ // but we still want to prevent anything else from executing.
40
+ @Suppress("CallToPrintStackTrace")
41
+ override fun quitSynchronous() {
42
+ try {
43
+ val mIsFinished = messageQueueThread.javaClass.getDeclaredField("mIsFinished")
44
+ mIsFinished.isAccessible = true
45
+ mIsFinished.set(messageQueueThread, true)
46
+ mIsFinished.isAccessible = false
47
+ } catch (e: NoSuchFieldException) {
48
+ e.printStackTrace()
49
+ } catch (e: IllegalAccessException) {
50
+ e.printStackTrace()
51
+ }
52
+ }
53
+
54
+ override fun getPerfStats(): MessageQueueThreadPerfStats? = messageQueueThread.getPerfStats()
55
+
56
+ override fun resetPerfStats() {
57
+ messageQueueThread.resetPerfStats()
58
+ }
59
+ }
@@ -0,0 +1,39 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.ReactPackage
5
+ import com.facebook.react.bridge.NativeModule
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.module.annotations.ReactModule
8
+ import com.facebook.react.module.annotations.ReactModuleList
9
+ import com.facebook.react.module.model.ReactModuleInfo
10
+ import com.facebook.react.module.model.ReactModuleInfoProvider
11
+
12
+ @ReactModuleList(nativeModules = [WorkletsModule::class])
13
+ class WorkletsPackage : BaseReactPackage(), ReactPackage {
14
+
15
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
16
+ if (name == WorkletsModule.NAME) WorkletsModule(reactContext) else null
17
+
18
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
19
+ val moduleList: Array<Class<*>> = arrayOf(WorkletsModule::class.java)
20
+
21
+ val reactModuleInfoMap = HashMap<String, ReactModuleInfo>()
22
+ for (moduleClass in moduleList) {
23
+ val reactModule: ReactModule =
24
+ checkNotNull(moduleClass.getAnnotation(ReactModule::class.java))
25
+
26
+ reactModuleInfoMap[reactModule.name] =
27
+ ReactModuleInfo(
28
+ reactModule.name,
29
+ moduleClass.name,
30
+ reactModule.canOverrideExistingModule,
31
+ reactModule.needsEagerInit,
32
+ reactModule.isCxxModule,
33
+ true
34
+ )
35
+ }
36
+
37
+ return ReactModuleInfoProvider { reactModuleInfoMap }
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ package com.swmansion.worklets.runloop
2
+
3
+ import com.facebook.jni.HybridData
4
+ import com.facebook.proguard.annotations.DoNotStrip
5
+
6
+ @Suppress("KotlinJniMissingFunction")
7
+ class AnimationFrameCallback @DoNotStrip private constructor(
8
+ @field:DoNotStrip private val mHybridData: HybridData
9
+ ) {
10
+ external fun onAnimationFrame(timestampMs: Double)
11
+ }
@@ -0,0 +1,106 @@
1
+ package com.swmansion.worklets.runloop
2
+
3
+ import android.os.SystemClock
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.modules.core.ReactChoreographer
6
+ import com.facebook.react.uimanager.GuardedFrameCallback
7
+ import java.util.concurrent.atomic.AtomicBoolean
8
+
9
+ class AnimationFrameQueue(reactApplicationContext: ReactApplicationContext) {
10
+
11
+ private var mFirstUptime = SystemClock.uptimeMillis()
12
+ private var mSlowAnimationsEnabled = false
13
+ private var lastFrameTimeMs = 0.0
14
+ private var mAnimationsDragFactor = 1
15
+
16
+ /// ReactChoreographer is
17
+ /// [thread safe](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/ReactChoreographer.kt#L21).
18
+ private val mReactChoreographer: ReactChoreographer = ReactChoreographer.getInstance()
19
+ private val mChoreographerCallback: GuardedFrameCallback =
20
+ object : GuardedFrameCallback(reactApplicationContext) {
21
+ override fun doFrameGuarded(frameTimeNanos: Long) {
22
+ executeQueue(frameTimeNanos)
23
+ }
24
+ }
25
+ private val mCallbackPosted = AtomicBoolean()
26
+ private val mPaused = AtomicBoolean()
27
+ private val mFrameCallbacks = mutableListOf<AnimationFrameCallback>()
28
+
29
+ fun resume() {
30
+ if (mPaused.getAndSet(false)) {
31
+ scheduleQueueExecution()
32
+ }
33
+ }
34
+
35
+ fun pause() {
36
+ synchronized(mPaused) {
37
+ if (!mPaused.getAndSet(true) && mCallbackPosted.getAndSet(false)) {
38
+ mReactChoreographer.removeFrameCallback(
39
+ ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, mChoreographerCallback
40
+ )
41
+ }
42
+ }
43
+ }
44
+
45
+ fun requestAnimationFrame(animationFrameCallback: AnimationFrameCallback) {
46
+ synchronized(mFrameCallbacks) {
47
+ mFrameCallbacks.add(animationFrameCallback)
48
+ }
49
+ scheduleQueueExecution()
50
+ }
51
+
52
+ fun enableSlowAnimations(slowAnimationsEnabled: Boolean, animationsDragFactor: Int) {
53
+ mSlowAnimationsEnabled = slowAnimationsEnabled
54
+ mAnimationsDragFactor = animationsDragFactor
55
+ if (slowAnimationsEnabled) {
56
+ mFirstUptime = SystemClock.uptimeMillis()
57
+ }
58
+ }
59
+
60
+ private fun scheduleQueueExecution() {
61
+ synchronized(mPaused) {
62
+ if (!mPaused.get() && !mCallbackPosted.getAndSet(true)) {
63
+ mReactChoreographer.postFrameCallback(
64
+ ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, mChoreographerCallback
65
+ )
66
+ }
67
+ }
68
+ }
69
+
70
+ private fun executeQueue(frameTimeNanos: Long) {
71
+ val currentFrameTimeMs = calculateTimestamp(frameTimeNanos)
72
+ if (currentFrameTimeMs <= lastFrameTimeMs) {
73
+ // It is possible for ChoreographerCallback to be executed twice within the same frame
74
+ // due to frame drops. If this occurs, the additional callback execution should be ignored.
75
+ mCallbackPosted.set(false)
76
+ scheduleQueueExecution()
77
+ return
78
+ }
79
+
80
+ val frameCallbacks = pullCallbacks()
81
+ mCallbackPosted.set(false)
82
+
83
+ lastFrameTimeMs = currentFrameTimeMs
84
+ for (callback in frameCallbacks) {
85
+ callback.onAnimationFrame(currentFrameTimeMs)
86
+ }
87
+ }
88
+
89
+ private fun pullCallbacks(): List<AnimationFrameCallback> {
90
+ synchronized(mFrameCallbacks) {
91
+ val frameCallbacks = mFrameCallbacks.toList()
92
+ mFrameCallbacks.clear()
93
+ return frameCallbacks
94
+ }
95
+ }
96
+
97
+ private fun calculateTimestamp(frameTimeNanos: Long): Double {
98
+ val nanosecondsInMilliseconds = 1000000.0
99
+ var currentFrameTimeMs = frameTimeNanos / nanosecondsInMilliseconds
100
+ if (mSlowAnimationsEnabled) {
101
+ currentFrameTimeMs =
102
+ mFirstUptime + (currentFrameTimeMs - mFirstUptime) / mAnimationsDragFactor
103
+ }
104
+ return currentFrameTimeMs
105
+ }
106
+ }
@@ -0,0 +1,164 @@
1
+ package com.swmansion.worklets
2
+
3
+ import com.facebook.jni.HybridData
4
+ import com.facebook.proguard.annotations.DoNotStrip
5
+ import com.facebook.react.bridge.Callback
6
+ import com.facebook.react.bridge.LifecycleEventListener
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableNativeArray
10
+ import com.facebook.react.bridge.ReadableNativeMap
11
+ import com.facebook.react.bridge.queue.MessageQueueThread
12
+ import com.facebook.react.common.annotations.FrameworkAPI
13
+ import com.facebook.react.module.annotations.ReactModule
14
+ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
15
+ import com.facebook.soloader.SoLoader
16
+ import com.swmansion.worklets.runloop.AnimationFrameCallback
17
+ import com.swmansion.worklets.runloop.AnimationFrameQueue
18
+ import java.util.concurrent.atomic.AtomicBoolean
19
+
20
+ @Suppress("KotlinJniMissingFunction")
21
+ @ReactModule(name = WorkletsModule.NAME)
22
+ class WorkletsModule(reactContext: ReactApplicationContext) :
23
+ NativeWorkletsModuleSpec(reactContext), LifecycleEventListener {
24
+
25
+ companion object {
26
+ const val NAME = "WorkletsModule"
27
+
28
+ init {
29
+ SoLoader.loadLibrary("worklets")
30
+ }
31
+ }
32
+
33
+ @field:DoNotStrip
34
+ @Suppress("unused")
35
+ private var mHybridData: HybridData? = null
36
+
37
+ @Suppress("unused")
38
+ protected fun getHybridData(): HybridData? = mHybridData
39
+
40
+ init {
41
+ reactContext.assertOnJSQueueThread()
42
+ }
43
+
44
+ private val mMessageQueueThread = WorkletsMessageQueueThread()
45
+ private val mAndroidUIScheduler = AndroidUIScheduler(reactContext)
46
+ private val mAnimationFrameQueue = AnimationFrameQueue(reactContext)
47
+ private val mWorkletsNetworking = WorkletsNetworking()
48
+ private var mSlowAnimationsEnabled = false
49
+
50
+ /**
51
+ * Invalidating concurrently could be fatal. It shouldn't happen in a normal flow, but it doesn't
52
+ * cost us much to add synchronization for extra safety.
53
+ */
54
+ private val mInvalidated = AtomicBoolean(false)
55
+
56
+ @OptIn(FrameworkAPI::class)
57
+ private external fun initHybrid(
58
+ bundleModeEnabled: Boolean,
59
+ jsContext: Long,
60
+ messageQueueThread: MessageQueueThread,
61
+ jsCallInvokerHolder: CallInvokerHolderImpl,
62
+ androidUIScheduler: AndroidUIScheduler,
63
+ scriptBufferWrapper: ScriptBufferWrapper?
64
+ ): HybridData
65
+
66
+ @OptIn(FrameworkAPI::class)
67
+ @ReactMethod(isBlockingSynchronousMethod = true)
68
+ override fun installTurboModule(bundleModeEnabled: Boolean): Boolean {
69
+ val context = reactApplicationContext
70
+
71
+ context.assertOnJSQueueThread()
72
+
73
+ val jsContext = checkNotNull(context.javaScriptContextHolder).get()
74
+ val jsCallInvokerHolder = JSCallInvokerResolver.getJSCallInvokerHolder(context)
75
+
76
+ val sourceURL = context.sourceURL
77
+
78
+ val scriptBufferWrapper: ScriptBufferWrapper? = if (bundleModeEnabled) {
79
+ ScriptBufferWrapper(sourceURL!!, context.assets)
80
+ } else null
81
+
82
+ mHybridData =
83
+ initHybrid(
84
+ bundleModeEnabled,
85
+ jsContext,
86
+ mMessageQueueThread,
87
+ jsCallInvokerHolder,
88
+ mAndroidUIScheduler,
89
+ scriptBufferWrapper)
90
+ return true
91
+ }
92
+
93
+ fun abortRequest(runtimeId: Int, requestIdAsDouble: Double) {
94
+ mWorkletsNetworking.jsiAbortRequest(runtimeId, requestIdAsDouble)
95
+ }
96
+
97
+ fun clearCookies(callback: Callback) {
98
+ mWorkletsNetworking.jsiClearCookies(callback)
99
+ }
100
+
101
+ fun sendRequest(
102
+ runtimeWrapper: WorkletRuntimeWrapper,
103
+ method: String,
104
+ url: String,
105
+ requestIdAsDouble: Double,
106
+ headers: ReadableNativeArray,
107
+ data: ReadableNativeMap,
108
+ responseType: String,
109
+ useIncrementalUpdates: Boolean,
110
+ timeoutAsDouble: Double,
111
+ withCredentials: Boolean
112
+ ) {
113
+ mWorkletsNetworking.jsiSendRequest(
114
+ runtimeWrapper,
115
+ method,
116
+ url,
117
+ requestIdAsDouble,
118
+ headers,
119
+ data,
120
+ responseType,
121
+ useIncrementalUpdates,
122
+ timeoutAsDouble,
123
+ withCredentials)
124
+ }
125
+
126
+ fun requestAnimationFrame(animationFrameCallback: AnimationFrameCallback) {
127
+ mAnimationFrameQueue.requestAnimationFrame(animationFrameCallback)
128
+ }
129
+
130
+ /** @noinspection unused */
131
+ @DoNotStrip
132
+ fun isOnJSQueueThread(): Boolean = reactApplicationContext.isOnJSQueueThread
133
+
134
+ fun toggleSlowAnimations() {
135
+ val animationsDragFactor = 10
136
+ mSlowAnimationsEnabled = !mSlowAnimationsEnabled
137
+ mAnimationFrameQueue.enableSlowAnimations(mSlowAnimationsEnabled, animationsDragFactor)
138
+ }
139
+
140
+ override fun invalidate() {
141
+ if (mInvalidated.getAndSet(true)) {
142
+ return
143
+ }
144
+ if (mHybridData != null && mHybridData!!.isValid) {
145
+ // We have to destroy extra runtimes when invalidate is called. If we clean
146
+ // it up later instead there's a chance the runtime will retain references
147
+ // to invalidated memory and will crash on its destruction.
148
+ invalidateCpp()
149
+ }
150
+ mAndroidUIScheduler.deactivate()
151
+ }
152
+
153
+ private external fun invalidateCpp()
154
+
155
+ override fun onHostResume() {
156
+ mAnimationFrameQueue.resume()
157
+ }
158
+
159
+ override fun onHostPause() {
160
+ mAnimationFrameQueue.pause()
161
+ }
162
+
163
+ override fun onHostDestroy() {}
164
+ }